710 likes | 906 Views
Program Security Part 2. Trying to Prevent Attacks Due to Insecure Code. Assessing the Impact of Memory Corruption. Let's try to access the risk these memory corruption vulnerabilities represent. A number of factors affect how exploitable a vulnerability is.
E N D
Program SecurityPart 2 Trying to Prevent Attacks Due to Insecure Code
Assessing the Impact of Memory Corruption • Let's try to access the risk these memory corruption vulnerabilities represent. • A number of factors affect how exploitable a vulnerability is. • By being aware of these factors, code auditors can estimate how serious a vulnerability is and the extent to which it can be exploited. • Can it be used to crash the application? • Can arbitrary code be run?
Assessing the Impact of Memory Corruption • The only way to know for certain is to write a proof-of-concept exploit, but that approach can be far too time consuming for even a moderate-sized application assessment. • Instead, we can reasonably estimate exploitability by answering a few questions about the resulting memory corruption. • This approach is not as definitive as a proof-of-concept exploit, but it's far less time consuming, making it adequate for most assessments.
The Real Cost of Fixing Vulnerabilities • Indeed history has shown that attackers and security researches alike have come up with ingenious ways to exploit the seemingly unexploitable. • Therefore, most auditors think that software vendors should treat all issues as high priority,
The Real Cost of Fixing Vulnerabilities • You might be surprised at the amount of resistance you can encounter when disclosing vulnerabilities to vendors - even vendors who specifically hired you to perform an assessment. • Vendors often say that potential memory corruption bugs aren't exploitable or aren't problems for some reason or another. • However, memory corruption affects an application at its most basic level, so all instances need to be given serious consideration.
Protection Mechanisms • The attack basics covered so far represent viable exploitation techniques for some contemporary systems, but the security landscape is changing rapidly. • Modern operating systems often include preventive technologies to make it difficult to exploit vulnerabilities such as buffer overflows. • These technologies typically reduce an attacker's chance of exploiting a bug or at least reduce the chance that a program can be constructed to reliably exploit a bug on a target host.
Protection Mechanisms • We'll focus on technical details of common anticorruption protections and address potential and real weaknesses in these mechanisms. • This discussion isn't a comprehensive study of protection technologies, but it does touch on the most commonly deployed ones. • For each protection technology, we'll also consider the limitations of the technology since unfortunately, there are also limitations.
Heap Implementation Hardening • Recall that heap overflows are typically exploited through the unlinking operations performed by the system's memory allocation and deallocation routines. • The list operations in memory management routines can be leveraged to write to arbitrary locations in memory and seize complete control of the application. • In response to this threat, a number of systems have hardened their heap implementations to make them more resistant to exploitation.
Heap Implementation Hardening • Windows XP SP2 and later have implemented various protections to ensure that heap operations don't inadvertently allow attackers to manipulate the process in a harmful manner. • These mechanisms include the following: • An 8-bit cookie is stored in each heap header structure. • An XOR operation combines this cookie with a global heap cookie, and the heap chunk's address divided by 8. • If the resulting value is not 0, heap corruption has occurred. • Because the address of the heap chunk is used in this operation, cookies shouldn't be vulnerable to brute force attacks.
Heap Implementation Hardening • Checks are done whenever an unlink operation occurs to ensure that the previous and next elements are indeed valid. • Specifically, both the next and previous elements must point back to the current element about to be unlinked. • If they don't, the heap is assumed to be corrupt and the operation is aborted. • The UNIX glibc heap implementation has also been hardened to prevent easy heap exploitation. • The glibc developers have added unlink checks to their heap management code, similar to the Windows SP2 defenses.
Limitations of Heap Implementation Hardening • Note that if a Windows user fails to install SP2, this protection is not implemented. • Unfortunately, many users fail to install patches. • Heap protection technologies aren't perfect. • Most have weaknesses that still allow attackers to leverage heap data structures for reliable (or relatively reliable) exploitation. • There are several published works on defeating both Windows and UNIX heap protections.
Limitations of Heap Implementation Hardening • Some published works on defeating both Windows and UNIX heap protections: • "Defeating Microsoft Windows XP SP2 Heap Protection and DEP ByPass" by Alexander Anisimov http://www.maxpatrol.com/defeating-xpsp2-heap-protection.htm • "A New Way to Bypass Windows Heap Protections" by Nicholas Falliere http://www.securityfocus.com/infocus/1846/ • "Windows Heap Exploitation" by Oded Horovitz and Matt Connover http://www.cybertech.net/~sh0ksh0k/heap/XPSP2%20Heap%20Exploitation.ppt
Limitations of Heap Implementation Hardening • UNIX glibc implementations have undergone similar scrutiny. • One useful resource is "The Malloc Maleficarum" by Phantasmal Phantasmagoria http://www.securityfocus.com/archive/1/413007/30/0/threaded • The most important limitation of these heap protection mechanisms is that they protect only the internal heap management structures. • They don't prevent attackers from modifying application data on the heap. • If you are able to modify other meaningful data, exploitation is usually just a matter of time and effort.
Limitations of Heap Implementation Hardening • Modifying program variables is difficult, however, as it requires specific variable layouts. • An attacker can create these layouts in many applications, but it isn't always a reliable form of exploitation - especially in multithreaded applications. • Another point to keep is mind is that it is not uncommon for applications to implement their own memory management strategies on top of the system allocation routines.
Limitations of Heap Implementation Hardening • In this situation, the application in question usually requests a page or series of pages from the system at once and then manages them internally with its own algorithm. • This can be advantageous for attackers because custom memory management algorithms are often unprotected, leaving them vulnerable to variations on classic heap overwrite attacks.
Nonexecutable Stack And Heap Protection • Many CPUs provide fine-grained protection for memory pages, allowing the CPU to mark a page in memory as readable, writable, or executable. • If the program keeps its code and data completely separate, it's possible to prevent shellcode from running by marking data pages as nonexecutable. • By enforcing nonexecutable protections, the CPU prevents the most popular exploitation method, which is to transfer control flow for a location in memory where attacker-created data already resides.
Nonexecutable Stack And Heap Protection • Intel CPUs didn't enforce nonexecutable memory pages until 2004. • Some interesting workarounds were developed to overcome this limitation by other companies. • One of the most notable ones was by the PaX Development team. • See documentation at http://pax.grsecurity.net/
Limitations of Nonexecutable Stack And Heap Protection • Because nonexecutable memory is enforced by the CPU, by bypassing this protection directly isn't feasible - generally, the attacker is completely incapacitated from directing execution to a location on the stack or the heap. • However, this does not prevent attackers from returning to useful code in the executable code sections, whether it's in the application being exploited or a shared library. • One popular technique to circumvent these protections is to have a series of return addresses constructed on the stack so that the attacker can make multiple calls to useful API functions.
Limitations of Nonexecutable Stack And Heap Protection • Often, attackers can return to an API function for unprotecting a region of memory with data they control. • This marks the target page as executable and disables the protection, allowing the exploit to run its own shellcode. • In general, this protection mechanism makes exploiting protected systems more difficult, but sophisticated attackers can usually find a way around it. • With a little creativity, the existing code can be spliced, diced, and coerced into serving the attacker's purpose.
Address Space Layout Randomization (ASLR) • ASLR is a technology that attempts to mitigate the threat of buffer overflows by randomizing where application data and code is mapped at runtime. • Essentially, data and code sections are mapped to a somewhat random memory location where they are loaded. • Because a critical part of buffer overflow exploitation involves overwriting key data structures or returning to specific places in memory, ASLR should, in theory, prevent reliable exploitation because attackers can no longer rely on static addresses.
Limitations of ASLR • Although ASLR is a form of security by obscurity, it's a highly effective technique for preventing exploitation, especially when used with some of the other preventive technologies already discussed. • However, ASLR can still be defeated. • Defeating ASLR especially relies on finding a weak point in the ASLR implementation. • Attackers usually attempt to adopt one of the following approaches:
Limitations of ASLR - Attack Approaches • Find something in memory that's in a static location despite the ASLR. No matter what the static element is, it's probably useful in one way or another. • Examples of statically located elements might include • Base executables that don't contain relocation information so the loader might not be able to relocate it. • Specialized data structures present in all mapped processes such as the Windows PEB or the Linux vsyscall page. • The loader itself • Nonrelocatable shared libraries • If ASLR fails to randomize any specific part of the process, it can be relied on and potentially able to undermine the ASLR protection.
Limitations of ASLR - Attack Approaches • Brute force where possible. • In a lot of cases, data elements are shifted in memory, but not by a large amount. • An example is the current Linux executable ASLR maps the stack at a random location • However, closer inspection on the code shows these mapping include only 256 possible locations. • This small set of possible locations doesn't provide for a large randomness factor and most ASLR implementations don't randomize a child process' memory layout. • Thus, an attacker can send requests for each possible offset and eventually achieve successful exploitation when the correct offset is found.
SafeSEH • Modern Windows systems (XP SP2+, Windows 2003, Vista) implement protection mechanism for the SEH structures located on the stack. • When an exception is triggered, the exception handler targets addresses are examined before they are called to ensure that each is a valid exception handler routine. • At this time, the following procedure determines an exception handler's validity: 1. Get the exception handler's address, and determine which module (DLL or executable) the handler address is pointing into.
SafeSEH 2. Check if there is an exception table registered. • An exception table is a table of valid exception handlers that can legitimately be entered in an _EXCEPTION_REGISTRATION structure. • This table is optional and modules might omit it. • In this case, the handler is assumed to be valid and can be called. 3. If the exception table exists and the handler address in the _EXCEPTION_REGISTRATION structure doesn't match a valid handler entry, the structure is deemed corrupt and the handler isn't called. • SafeSEH protection is a good complement to the stack cookies used in recent Windows releases, in that it prevents attackers from using SEH overwrites as a method for bypassing the stack cookie protection.
Limitations of SafeSEH • However, as with other protection mechanisms, it has had weaknesses in the past. • David Litchfield of Next Generation Security Software (NGSSoftware) wrote a paper detailing some problems with early implementations of SafeSEH that have since been addressed. • Primary methods for bypassing SafeSEH include returning to a location in memory that doesn't belong to any module (such a PEB), returning into modules without an registered exception table, or abusing defined exception handlers that might allow indirect running of arbitrary code.
Function Pointer Obfuscation • Long-lived function pointers are often the target of memory corruption exploits because they provide a direct method for seizing control of program execution. • One method of preventing this attack is to obfuscate any sensitive pointers stored in globally visible data structures. • This protection mechanism doesn't prevent memory corruption, but it does reduce the probability of a successful exploit for any attack other than a denial of service.
Function Pointer Obfuscation • We saw earlier that an attacker might be able to leverage function pointers in the PEB of a running Windows process. • To help mitigate this attack, Microsoft is now using the EncodePointer, DecodePointer, EncodeSystemPointer, and DecodeSystemPointer functions to obfuscate many of these values. • These functions obfuscate a pointer by combining its pointer value with a secret cookie value using an XOR exception • Recent versions of Windows also use this anti-exploitation technique in parts of the heap implementation.
Limitations of Function Pointer Obfuscation • This technology certainly raises the bar for exploit developers, especially when combined with other technologies, such as ASLR and nonexecutable memory pages. • However, it is not a complete solution in itself and has only limited use. • Attackers can still overwrite application-specific function pointers, as compilers currently don't encode function pointers the application uses. • An attacker might also be able to overwrite normal unencoded variables that eventually provide execution control through a less direct vector.
Limitations of Function Pointer Obfuscation • Finally, attackers might identify circumstances that redirect execution control in a limited but useful way. • For example, when user-controlled data is in close proximity to a function pointer, just corrupting the low byte of an encoded function pointer might give attackers a reasonable chance of running arbitrary code, • This is especially true when they can make repeated exploit attempts until a successful value is identified.
Preventing Buffer Overflow Attacks • Main problem: • strcpy(), strcat(), sprintf() have no range checking. • “Safe” versions strncpy(), strncat() are misleading • strncpy() may leave buffer unterminated. • strncpy(), strncat() encourage off by 1 bugs. • Defenses: • Type safe languages (Java, ML). Legacy code? • Mark stack as non-executable or random stack location. • Static source code analysis. • Run time checking: StackGuard, Libsafe, SafeC, (Purify).
Finding Buffer Overflows • To find buffer overflows on a local machine • Run web server on local machine • Issue requests with long tags Say, make all long tags end with “$$$$$” • If web server crashes, search core dump for “$$$$$” to find an overflow location • Some automated tools exist to find buffer overflows
Finding Buffer Overflows • eEye Retina identifies known and zero day vulnerabilities plus provides security risk assessment, enabling security best practices, policy enforcement, and regulatory audits. • Then use disassemblers and debuggers (e.g. IDA-Pro) to construct exploit • See http://www.hex-rays.com/idapro/ida-executive.pdf
Finding Overflows • Obtain local copy of target software. (e.g. web server) • Run on long, distinctive inputs, until program dumps core. • Search core dump for inputs to find overflow location. • Many other approaches: • Google "finding buffer overflows" (obviously!) • In Safari, search on "buffer overflow attack" - get 422 hits • BugTraq: See next slide
BugTraq • BugTraq is an electronic mailing list dedicated to issues about computer security. • On-topic issues are new discussions about vulnerabilities, vendor security-related announcements, methods of exploitation, and how to fix them. • It is a high-volume mailing list, and almost all new vulnerabilities are discussed there. • See general articles at http://www.securityfocus.com/ • See BugTraq at http://www.securityfocus.com/archive/1
Preventing Overflows • Buffer overflows happen because C, C++, and some other languages lack array bound checking. • So don’t use C, C++, or the other languages! • Stuck with them? • What can be done? Use • A non-executable stack • Randomization • Source code analysis • Run-time checking
Nonexecutable Stack Overflows • The “famous” stack-smashing attack puts code on the stack, so this fails if the stack is not executable. • If goal is, e.g., to get a shell, no particular reason we need to call our own code. • Instead: After the return from func(str) call exec(“/bin/sh”)
Randomization • Address space randomization: change locations of the stack, heap, data segment, shared libs… • Both make exploits harder, but see: www.stanford.edu/~blp/papers/asrandom.pdf www.usenix.org/events/sec05/tech/full_papers/sovarel/sovarel.pdf • Instruction set randomization: changes opcodes on machine. Supported by some processors! • Then injected code usually won’t run… • return-to-libc has to find it first…
Use of Canaries • Coal Miner Approach: Take a canary into a mine and watch for the canary's health to change. • When it does, get out fast! • A similar approach will work to prevent a stack from being corrupted. • In each stack frame, put a "canary" - something that can be located easily. • Before using the frame, check for the canary and exit gracefully if it is found.
Stack Cookies • Stack cookies, also known as canary values, are a method devised to detect and prevent exploitation of a buffer overflow on the stack, • Stack cookies are a complete solution present in recent default applications and libraries shipped with Windows XP SP2 (Service Pack 2) and later. • There are also several UNIX implementations of stack cookie protections, most notably ProPolice and Stackguard.
Stack Cookies • Stack cookies work by inserting a random 32-bit value (usually generated at runtime) on the stack immediately after the saved return address and saved frame pointer, but before the local variables in each stack frame as shown here: protected function stack frame Ordinary function stack frame
Stack Cookies • The cookie is inserted when the function is entered and is checked immediately before the function returns. • If the cookie value has been altered, the program can infer that the stack has been corrupted and take appropriate action. • This response prevents traditional stack overflows from being exploitable, as the corrupted return address is never used.
Limitations Of Stack Cookies • Stack cookies are effective, but not foolproof. • It prevents overwriting the saved frame pointer and saved return address. • But. it doesn't protect against overwriting adjacent local variables. • We saw earlier how overwriting local variables can subvert system security, especially when pointer values are corrupted which the function users to modify data. • Modification of these pointer values usually results in the attacker seizing control of the application by overwriting a function pointer or other useful values.
Limitations Of Stack Cookies • One way to prevent local variable corruption, is to reorder local variables as this can minimize the risk of adjacent variable overwriting. • Many current stack protection systems do this. • Another attack is to write past the stack cookie and overwrite the parameters to the current function. • The attacker corrupts the stack cookie by overwriting function parameters, but the goal of the attack is to not let the function return. • In certain cases, overwriting function parameters allows the attacker to gain control of the application before the function returns, thus circumventing the stack cookie protection.
Examples of Canaries • Random canary: • Choose random string at program startup. • Insert canary string into every stack frame. • Verify canary before returning from function. • To corrupt random canary, attacker must learn current random string. • Terminator canary: • Canary = 0, newline, linefeed, EOF • String functions will not copy beyond terminator. • Hence, attacker cannot use string functions to corrupt stack.
Limitations Of Stack Cookies • Although this technique seems as though it would be useful to attackers, optimization can sometimes inadvertently eliminate the chance of a bug being exploited. • When a variable value is used frequently, the compiler usually generates code that reads it off the stack once and then keeps it in a register for the duration of the function or the part of the function in which the value is used repeatedly. • So, even though an argument or local variable might be accessed frequently after an overflow is triggered, attackers might not be able to use that argument to perform arbitrary overwrites.
Limitations Of Stack Cookies • Another similar technique on Windows is to not worry about the saved return address and instead shoot for the SEH overwrite. • This way, the attacker can corrupt SEH records and trigger an access violation before the currently running function returns. • There, attacker-controlled code runs and the overflow is never detected. • Finally, note that stack cookies are a compile-time solution and might not be a realistic option if developers can't recompile the whole application.
Limitations Of Stack Cookies • The developers might not have access to all the source code, such as code in commercial libraries. • There might also be issues with making changes to the build environment for a large application, especially with hand-optimized components. • What other protection mechanisms exist?
Stackguard • GCC patch, use random canaries • Program must be recompiled. • Minimal performance effects: 8% for Apache. • Newer version: PointGuard. • Protects function pointers and setjmp buffers by placing canaries next to them. • More noticeable performance effects. • Note: Canaries don’t offer foolproof protection. • Some stack smashing attacks can leave canaries untouched.
StackGuard Variants - ProPolice • ProPolice (IBM) - gcc 3.4.1. (-fstack-protector) • Rearrange stack layout to prevent ptr overflow. args No arrays or pointers StringGrowth ret addr SFP CANARY arrays StackGrowth local variables Ptrs, but no arrays