Week 14.a CS 5600 12/07 2021 On the board ------------ 1. Last time 2. Stack frame, revisited 3. Stack smashing 4. Defenses --------------------------------------------------------------------------- 1. Last time -- wrap up authentication --rubber hose attack -- access control; subj accesses obj, pass or reject? -- Unix access control: UID/GID, file permission, process -- setuid: a (dangerous) way to raise privilege level -- there are multiple attacks 2. stack frame & calling convention, revisited -- revisit the first handout (week1.a) -- recall what stack frame looks like -- recall how calling convention works 3. Stack smashing There are many ways to attack computers. Today we study the "classic" method: buffer overflow. ('buffer overflow' is one way to conduct a stack smashing attack.) Introduction to buffer overflow attacks This method has been adapted to many different types of attacks, but the concepts are similar. We study this attack not to teach you all to become hackers but rather to educate you about vulnerabilities: what they are, how they work, and how to defend against them. Please remember: _although the approaches used to break into computers are very interesting, breaking in to a computer that you do not own is, in most cases, a criminal act_. A. intro of the architecture an echo server (run by a privileged account) accepts network request of "(len, msg)", echos the received msg a client (remote) connects to server, send request "(len, msg)" [demo] [NOTE: fork/exec separation is what allows us to write tcpserve: after the fork() but before exec() of buggy-server, child rearranges its file descriptors to be the socket itself. Also, this sample code gives you a chance to see sockets in action.] --user "ubuntu" (a superuser): runs (buggy) server --user "nobody56": runs honest client everything is fine --user "noboday56": runs dishonest client open a shell that has superuser privileges (because the buggy server is running under a superuser) --note: if the user/syscall interface doesn't check its arguments properly, can buffer overflow that interface to take over the kernel. B. Let’s examine a vulnerable server, buggy−server.c (see handout week14.a) C. Let’s examine how an unscrupulous element (a hacker, a script kiddie, a worm, and so on) might exploit the server, exploit.c (see handout week14.a) 4. Defenses Three requirements to conduct the previous attack: -- (a) code injection into stack -- (b) overwriting data on stack -- (c) knowing memory addresses of stack Three main defense approaches: -- nonexecutable stack (NX) [targeting (a)] -- stack canaries [targeting (b)] -- address randomization [targeting (c)] Then...how could the demo work? We return the stack address in the code... (regarding (c)) ...and we compile the code with gcc flags: "-fno-stack-protector -zexecstack" (never do this for a public-facing app!) [for (a) and (b)] A. nonexecutable stack --defenders create W ^ X policy (see below) so that memory cannot be both writable and executable. response: return-oriented programming (ROP) [see handout week14.a] smash the stack with a bunch of return addresses. each return address points to the needed instruction followed by "ret" (requires the attacker to have previously identified these instructions in the code, so the assumption is that the attacker has access to the source code or binary). not too hard in CISC code like on x86, where there are lots of sequences of code embedded in the binary, even sequences that the programmer didn't mean (because instructions are not fixed length). result: the control flow bounces around all of these byte sequences in memory, executing exactly what the attacker wanted, but not executing off of the stack. defending against ROP is hard (though if people use only safe languages, that is, languages that do bounds checking and other pointer checks, such attacks will be much, much harder) --ROP requires access to the source or binary, so maybe we can just make sure that binaries don't fall into the hands of attackers? --Well, no that doesn't work either. The technique of *Blind* Return-Oriented Programming (BROP), shows how to conduct attacks even when the binary isn't available and even on 64-bit machines. References: http://www.scs.stanford.edu/brop/ http://www.scs.stanford.edu/~sorbo/brop/bittau-brop.pdf [side note: When hacking software, there are three exploit scenarios: --Open-source (e.g., Apache) --Open-binary (e.g., Internet Explorer) --Closed-binary and source (e.g., some proprietary network service) ] --what is W ^ X? map the stack pages as non-executable, if the hardware allows it. --Hardware these days allows it: there's an NX bit in the page table entries. You generally want to set that bit. --The bummer with W ^ X used to be this: some languages not only don't need it but also are actively harmed by W ^ X. The core of the issue is that a program written in a safe language (Perl, Python, Java, etc.) does not need W ^ X whereas lots of C programs do. Meanwhile some machines *always* enforce W ^ X, even for programs that do not need it. Such enforcement constrains certain languages, namely those that need to do runtime code generation. B. stack canaries --what about the defense of canary values near the return address? StackGuard (in gcc), PaX, etc. --This can be defeated by "stack reading" [see handout] C. address randomization --what about the defense of address space layout randomization (ASLR)? This provides some help but obviously doesn't help our vulnerable server because our server tells the client where the buffer is. --And on 32-bit systems, the randomness can be defeated through brute force. There's only 20 bits of randomness conceivable (the VPN bits), and ASLR implementations left the top four alone, to avoid fragmenting VM. --Linux usually maps executable code in a fixed address and randomize dynamic libraries and other data memory regions. So, attackers can BROP the executable directly. --On 64-bit systems, this defense can be defeated, if the server simply reforks children instead of restarting. Another defense: don't use C! CPUs are so fast that a language with bounds checking probably isn't going to pay a huge performance penalty relative to one without bounds checks