index.md (9828B)
1 +++ 2 title = 'Defenses and bypassing them' 3 +++ 4 # Defenses and bypassing them 5 6 Techniques that can make attacks harder: 7 - protect sensitive data from leakage/corruption 8 - make using corruption for code execution harder 9 - detect undefined behavior 10 11 Static analysis: can we prove code never results in undefined behavior? 12 - incomplete due to halting problem 13 14 Dynamic instrumentation: runtime checks for undefined behavior 15 - negative performance impact 16 - false positives mean crashing a correct program 17 18 ![Defenses overview](defenses-overview.png) 19 20 ## Stack canaries 21 Value between local vars and return address, compiler initializes with random value in function prologue. 22 On return, check whether value is still the same, and crash if it isn't. 23 24 Enable with `-fstack-protector`. 25 `%fs:40` is randomized at thread creation, canary is never left in a register, and is moved to a position in the stack, as a local variable. 26 27 Can still exploit, if we can jump over the canary. E.g. in this example, overwrite the len variable so memcpy writes to it: 28 29 ```c 30 void echo(int fd) { 31 long len; 32 char name[64], reply[128]; 33 /* ... */ 34 read(fd, name, 128); 35 memcpy(reply + len, name, 64); 36 /* ... */ 37 } 38 ``` 39 40 Three approaches: 41 - jump over canary: corrupt pointer/index used to write memory, write target address past canary. need two writes - either two vulnerabilities or a loop. 42 - leaking canaries: leak canary, overwrite with same value. need leak vulnerability, ability to provide more input after leak and buffer overflow with new input. 43 - brute force canaries: guessing randomly out of 2⁶⁴ possibilities is impossible. but can brute force if we can corrupt part of canary, program restarts with same canary, and get feedback about crashes. 44 - overflow into canary by one byte, try all values and find which is correct for first byte (doesn't crash). Try next bytes until full canary found. 45 - max 8×2⁸ attempts needed 46 47 ## Data execution prevention (DEP) 48 OS marks data pages as non-executable (no-execute bit in page tables). 49 - attempt to execute data (like shellcode) gives segfault 50 51 W⊕X: no memory is both writable and executable. 52 53 We can still exploit this, but no longer by jumping to own shellcode - need to reuse existing code. 54 55 Can use shared library functions (return-to-libc), or chain together parts of code (return-oriented programming). 56 57 ### Ret2libc 58 On x86\_32, params pass on the stack, so params to function can be easily set. 59 60 In 64-bit, parameters are passed in registers, so only works if parameter is in %rdi. 61 Maybe reuse additional code to load desired %rdi value (e.g. with a piece of code that pops to %rdi and returns). 62 63 ### Return oriented programming (ROP) 64 Any sequence of instructions ending in RET is a 'gadget', which can be chained together. 65 Return oriented programming is a chain of such gadgets. 66 67 Might want some useful ones: 68 - load value to register (pop) 69 - read from memory (mov (reg), reg) 70 - write to memory (mov reg, (reg)) 71 - computation (add, sub, etc.) 72 - syscall 73 74 Given the right gadgets, can do arbitrary computation without any code. 75 Tools help generate ROP chains, e.g. [Ropper](https://github.com/sashs/Ropper) 76 77 If we run out of writable stack, build a new stack anywhere in memory (such as attacker-controlled heap memory). 78 Function epilogue can load stack pointer (by using it twice). 79 This is a "stack pivot". 80 81 If we can't find the right gadget, use libraries, especially libc. 82 Can jump into middle of an instruction. 83 84 ## Address space layout randomization (ASLR) 85 Randomizes memory addresses of code, data, heap, and stack. 86 Prevents attacker from finding code pointer to overwrite, or knowing what to overwrite it with. 87 88 Stack and heap randomized by OS alone: 89 - stack: determined by %rsp, set in `execve()` 90 - heap: brk() and mmap() return values 91 92 Code and data require compiler support 93 - absolute code/data refs would break with randomized addresses 94 - position independent code (PIC) forces all pointers to be relative to instruction pointer 95 - libraries used PIC to prevent clashes in address space 96 - executables can also use PIC (`-fPIE`) 97 98 Can still be attacked: 99 - relative pointers don't change 100 - if one pointer is leaked, all others can be computed 101 - so leak pointers from stack/heap, or use side channels to recover complete address space 102 103 ### Information hiding 104 ASLR: randomness limited to base: 105 - first shared object is loaded at random position 106 - next object located right below (lower addresses) the last object 107 - ⇒ all libraries located side by side at single random place 108 109 Shadow stack: 110 - move sensitive data (e.g. return address) on separate stack 111 - make sure shadow stack is protected 112 - real hiding: 113 - no pointer in memory should refer to secret location of shadow stack 114 - only dedicated register (e.g. `%gs:108`) points to shadow stack 115 - how to find it: 116 - address space typically has some holes 117 - don't look for hidden region, look for holes -- one larger and one smaller -- surrounding the region 118 - even if we remove all pointers, we still have the size of the hole left that indicates the hidden region 119 - repeatedly allocate large chunks of memory until we find the "right size": if allocation succeeds, hole it is that size or bigger. if fails, hole is smaller. 120 - ephemeral allocation: allocate and free within the request 121 - persistent allocation: allocate 'permanently' (can also use ephemeral and just not complete the request) 122 - steps: 123 1. determine large hole using ephemeral allocation 124 2. allocate large hole using persistent allocation 125 3. run ephemeral allocation algorithm again, giving us small hole 126 127 ## Control-flow integrity 128 Prevent turing completeness with ROP chains. 129 130 idea: 131 - only allow "legitimate branches and calls" 132 - i.e. those that follow control flow graph 133 - give valid targets a label, and check that we don't branch anywhere else 134 135 May be combined with runtime shadow stack. 136 137 in practice 138 - requires precise CFG (from source code or debug info) 139 - large performance overhead 140 - so, coarse-grained CFI: 141 - one common label for all call sires 142 - one common label for all entry points 143 144 gadgets left after this: 145 - entry point gadget - jump to entry point and go up to next indirect call/jump 146 - call site gadget - jump to call site, go to the next return 147 148 ### Data oriented programming 149 Perfect CFI means CFG is never violated. 150 But, data guides code through CFG, so by manipulating data we change control flow. 151 152 Attacker can overwrite data e.g. using buffer overflow, and overwritten data drives a dispatching data 153 - loop executes as often as attacker wants 154 - loop performs several operations, providing gadgets 155 - attacker controls values and pointers used in the operations 156 157 ![Data oriented programming example](dop-example.png) 158 159 Approach: 160 - find gadget dispatcher (attacker-controlled loop) 161 - identify and classify gadgets 162 - convert workload into a sequence of gadget operations 163 - build a sequence of buffers to trigger those operations 164 - send the buffers to the target machine 165 166 ## Sanitizers 167 Fully detect or mostly detect particular vulnerabilities. 168 Big performance overhead, so not really used in production settings, but useful for debugging and testing. 169 170 Sanitizers in GCC/LLVM 171 - ASan: buffer overflow, memory leak, use-after-free 172 - Leak sanitizer: memory leaks 173 - TSan: race conditions 174 - UBSan: undefined arithmetic 175 - MSan: uninitialized read 176 177 Address sanitizer (ASan): 178 - detects buffer overflow and use-after-free 179 - shadow memory tracks allocation status: 180 - add check before memory access 181 - shadow addr = (addr >> 3) + offset 182 - red zones between allocations 183 - deallocated memory in quarantine 184 - drawbacks: compatibility (if program depends on memory layout), performance, incomplete (no overflows in structs, misses overflows that jump over read zone, use-after-free still possible with memory massaging) 185 186 Memory sanitizer (MSan): 187 - detects uninitialized reads 188 - loading values allowed 189 - uninitialized status tracked in shadow memory 190 - 1-y-1 per-bit shadow mapping 191 - allocation: memory poisoned 192 - computation: poison propagated based on operation 193 - poison checked at: conditional branches, syscalls, pointer derefs 194 195 ### Research topics 196 Delta pointers: fast buffer overflow detection 197 - tagged pointers use some pointer bits for metadata 198 - checks are implicit using MMu 199 - cannot detect all cases (like underflow), but much better performance 200 201 SafeInit: automatically initialize to zero 202 - compiler: every local variable 203 - allocator: every heap allocation 204 - optimizations: 205 - only close to first use 206 - only one byte for strings 207 - dead store elimination: prevent initializations that are later overwritten in all cases 208 - rely on OS zeroing for large heap allocations 209 210 DangSan: prevents use after free 211 - invalidates dangling pointers 212 - keep list of pointers to each object 213 - on pointer assignment: keep track of new pointer to object 214 - free: set most significant bit of remaining pointers 215 - complications: 216 - which object does pointer point to? use shadow memory 217 - what if multiple threads copy pointers to same object? use lock-free data structure (it's a write-mostly workload, so use per-thread append-only log) 218 219 Type-after-type 220 - allow dangling pointers, but only to same type 221 - separate heap and stack for each type 222 - never reuse memory used for one type for another 223 - dangling pointer keeps pointing to same type 224 - challenge: type inference 225 - use static analysis to guess from context 226 - explicit type on stack and with new operator, trace result to pointer cast, or sizeof in malloc size 227 228 TypeSan 229 - type confusion mitigation requires knowing runtime type of object on static_cast 230 - use shadow memory, translate each pointer to set of allowable casts (which is determined at compile time)