Buffer Overflows - parisi/Risorse/ 3 Buffer Overflows a.k.a. Buffer Overrun A buffer overflow happens when a program attempts to read or write data outside of the memory allocated

  • Published on

  • View

  • Download


11Buffer OverflowsLearning objectivesUnderstand the definition of a buffer overflowLearn the importance of buffer overflowsKnow how buffer overflows happenKnow how to handle strings safely with regular "C" functionsLearn safer ways to manipulate strings and buffers2"C" Programming Issues:DefinitionImportanceExamplesFundamental "C" problemsSurvey of unsafe functionsRelated Issues: Truncation, Character EncodingSafe string librariesPreventing buffer overflows without programming23Buffer Overflowsa.k.a. "Buffer Overrun"A buffer overflow happens when a program attempts to read or write data outside of the memory allocated for that dataUsually affects buffers of fixed sizeSpecial case of memory management and input validation4Important Vulnerability TypeMost Common (over 60% of CERT advisories)Well understoodEasy to avoid in principleDont use "C" family languages, or be thoroughCan be tricky (off-by-one errors)Tedious to do all the checks properlyTemptation: "I don't need to because I control this data and I *know* that it will never be larger than this"Until a hacker figures out how to change it 35Example Overflowchar B[10]; B[10] = x; Array starts at index zeroSo B[10] is 11th elementOne byte outside the buffer is referencedOff-by-one errors are common and can be exploitable!6Off-by-one errors ProblemOff-by-one errors occur when a programmer takes the proper precautions in terms of bounds checking, but maybe puts a 512 where she should have put a 511.Can happen to the best programmers no matter how well-informed they are about buffer overflows.ConsequencesUsually off-by-one errors can do no more than crash the program. They can be made to compromise security-sensitive data. But any buffer overflow is a security risk.47Off-by-one errors -Recommendations.If you have a 512 byte buffer you can only store 511 characters in the string (the last character is a NULL).If you use scanf() to read into a buffer you also have to account for the NULL: use scanf(%511s, &My512ByteBuffer) instead of scanf(%512s, &My512ByteBuffer) which is unsafe.If you declare an array as int A[100], remember that you cannot access A[100], the highest index you can access is A[99] and the lowest is A[0].The best defense against off-by-one errors of any kind is a thorough combination of testing and code inspection.8Old code used for new purposes - Problem.Often old code is reused in new projects. Even if the old code was thoroughly tested and written in a safe manner, it might not have accounted for things that the new code expects it to support, like international character sets.59Old code used for new purposes - Consequences.HELLO in ASCII is 0x48-0x45-0x4C-0x4C-0x4FHELLO in UNICODE (http://www.newsbytes.com/news/02/174512.html) is 0x00-0x48- 0x00-0x45-0x00-0x4C-0x00-0x4C-0x00-0x4FThe old code might tell the new code to give it no more than 5 characters because it uses a 5-byte buffer. The new code gives it 5 characters, but in UNICODE instead of ASCII, so they fill 10 bytes. (The assumption that 5 characters = 5 bytes is a dangerous one.)This is more common and more easily exploitable than you might think. The Venetian exploit can hijack a program with a reasonably sized buffer overflow even if UNICODE format forces the attacker to have half of his attack code bytes be zeros.10Old code used for new purposes - Recommendations.Enumerate and challenge all assumptions that you have made about the interaction between old code and new.Test thoroughly.Test old code when you are using it for new purposes, even if you tested it before. If your software allows the user to use UNICODE then do all of the testing you did for ASCII with UNICODE as well.Include the old code in code inspection, even if you inspected it before.Test code on every type of platform it will likely be used on. Depending on how the processor arranges memory you might have an off-by-one error of a single byte that has no effect on program execution for a Sun processor but would have a noticeable effect on program execution for an Intel processor.611Other Examplefunction do_stuff(char *a) {char b[100];...strcpy(b, a); // (dest, source)...}What is the size of the string located at a?Is it even a null-terminated string?What if it was "strcpy(a, b);" instead?What is the size of the buffer pointed to by "a"?12What happens when memory outside a buffer is accessed?If memory does not exist:Bus errorIf memory protection denies access:Segmentation faultGeneral protection faultIf access is allowed, memory next to the buffer can be accessedHeapStacketc...713Real Life Example: efingerd.c, v. 1.6.2int get_request (int d, char buffer[], u_short len) {u_short i;for (i=0; i< len; i++) {...}buffer[i] = \0;return i;}What is the value of "i" at the end of the loop?Which byte just got zeroed?It is tricky even if you try to get things right...14Real Life Example: efingerd.c, v. 1.5CAN-2002-0423static char *lookup_addr(struct in_addr in){static char addr[100];struct hostent *he;he = gethostbyaddr(...)strcpy(addr, he->h_name);return addr;}How big is he->h_name? Who controls the results of gethostbyaddr?How secure is DNS? Can you be tricked into looking up a maliciously engineered value?815A Typical Stack ExploitThe stack contains:Parameters (arguments) to functionReturn AddressLocal variablesAnything pushed on the stackaddr[100+] overwrites the return addressaddr[0] typicallycontains exploitcode (external attack)Return address ischosen to point at exploitcode!ArgumentsReturn AddressLow AddressesHigh AddressesStack grows this wayaddr[99]addr[0]16Fundamental "C" ProblemsYou cannot know the length of buffers just from a pointerPartial solution: pass the length as a separate argument"C" string functions are not safeNo guarantees that the new string will be null-terminated!Doing all checks completely and properly is tedious and tricky917StrlenWhat happens when you call strlen on an improperly terminated string ?Strlen scans until a null character is foundCan scan outside buffer if string is not null-terminatedCan result in a segmentation fault or bus errorStrlen is not safe to call !Unless you positively know that the string is null-terminated...Are all the functions you use guaranteed to return a null-terminated string?18Strcpychar *strcpy(char *dst, const char *src);How can you use strcpy safely?Set the last character of src to NULAccording to the size of the buffer pointed to by src or a size parameter passed to youNot according to strlen(src) !Wide char array: sizeof(src)/sizeof(src[0]) -1 is the index of the last elementCheck that the size of the src buffer is smaller than or equal to that of the dst bufferOr allocate dst to be at least equal to the size of src1019Strncpychar *strncpy(char *dst, const char *src, size_t len);"len" is maximum number of characters to copy What is the correct value for len?Initial answer by most people: size of dstIf dst is an array, sizeof(dst)What if src is not NUL-terminated?Don't want to read outside of src bufferWhat is the correct value for "len" given that?Minimum buffer size of dst and src, -1 for NUL byteIf arrays,MIN(sizeof(dst), sizeof(src)) - 120Strncpy (Cont.)Other issue: "dst" is NUL-terminated only if less than "len" characters were copied!All calls to strncpy must be followed by a NUL-termination operation1121QuestionWhats wrong with this? function do_stuff(char * a) {char b[100];...strncpy(b, a, strlen(a));...}The string pointed to by "a" could be larger than the size of "b"!22Question / AnswerWhats wrong with this? function do_stuff(char * a) {char *b;...b = malloc(strlen(a)+1);strncpy(b, a, strlen(a));...}Are you absolutely certain that the string pointed to by "a" is NUL-terminated?1223Strlcpysize_t strlcpy(char *dst, const char *src, size_t size);Guarantees to null-terminate string pointed to by "dst" if "size">0The rest of the destination buffer is not zeroed as for strncpy, so better performance is obtained"size" can simply be size of dst (sizeof if an array)If all functions are guaranteed to null-terminate strings, then it is safe to assume src is null-terminatedNot safe if src is not null-terminated!See http://www.courtesan.com/todd/papers/strlcpy.html for benchmarks and more infosize_t strlcpy(char *dst, const char *src, size_t size);Guarantees to null-terminate string pointed to by "dst" if "size">0The rest of the destination buffer is not zeroed as for strncpy, so better performance is obtained"size" can simply be size of dst (sizeof if an array)If all functions are guaranteed to null-terminate strings, then it is safe to assume src is null-terminatedNot safe if src is not null-terminated!See http://www.courtesan.com/todd/papers/strlcpy.html for benchmarks and more info (Used in MacOS X, OpenBSD but not Linux)24Corrected efinger.c (v.1.6)sizeof is your friend, when you can use it (if an array)static char addr[100];he = gethostbyaddr(...);if (he == NULL)strncpy(addr,inet_ntoa(in),sizeof(addr));elsestrncpy(addr, he->h_name, sizeof(addr));What is still wrong?the last byte of addr is not zeroed, so this code can produce non-NUL-terminated strings!1325Strcatchar *strcat(char *s, const char *append);String pointed to by "append" is added at the end of the string contained in buffer "s"No check for size !Need to do all checks beforehandExample with arrays:if (sizeof(s)-strlen(s)-1 >= strlen(append))strcat(s, append);Need to trust that "s" and "append" are NUL-terminatedOr set their last byte to NUL before the checks and call26Strncatchar *strncat(char *s, const char *append, size_t count);No more than "count" characters are added, and then a NUL is addedCorrect call is complex:strncat(s, append, sizeof(s)-strlen(s)-1)Not a great improvement on strcat, because you still need to calculate correctly the count And then figure out if the string was truncatedNeed to trust that "s" and "append" are NUL-terminatedOr set their last byte to NUL before the checks and call1427Strlcatsize_t strlcat(char *dst, const char *src, size_t size);Call semantics are simple:strlcat(dst, src, dst_len);If an array:strlcat(dst, src, sizeof(dst));Safety: safe even if dst is not properly terminatedWill not read more than size characters from dst when looking for the append locationNot safe if src is not properly terminated!If dst is large and the buffer for src is small, then it could cause a segmentation fault or bus error, or copy confidential values28Issues with Truncating StringsSubsequent operations may fail or open up vulnerabilitiesIf string is a path, then it may not refer to the same thing, or be an invalid pathTruncation means you were not able to do what you wantedYou should handle that error instead of letting it go silently1529Truncation DetectionTruncation detection was simplified by strlcpy and strlcat, by changing the return valueThe returned value is the size of what would have been copied if the destination had an infinite sizeif this is larger than the destination size, truncation occurredSource still needs to be NUL-terminatedInspired by snprintf and vsprintf, which do the sameHowever, it still takes some consideration to make sure the test is correct:if (strlcpy(dest, src, sizeof(dest)) >= sizeof(dest)) goto toolong;30Multi-Byte Character EncodingsHandling of strings using variable-width encodings or multi-byte encodings is a probleme.g., UTF-8 is 1-4 bytes longHow long is the string?In bytesIn charactersOverflows are possible if size checks do not properly account for character encoding!.NET: System.String supports UTF-16Strings are immutable - no overflow possible there!1631SafestrFree library available at: http://zork.orgFeatures:Works on UNIX and WindowsBuffer overflow protectionString format protectionLimitations and differences:Does not handle multi-byte charactersLicense: binaries must reproduce a copyright noticeNUL characters have no special meaningMust use their library functions all the time (but conversion to regular "C" strings is easy)32Other Unsafe Functions: sprintf family int sprintf(char *s, const char *format, /* args*/ ...);Buffer "s" can be overflowedint snprintf(char *s, size_t n, const char *format, /* args*/ ...);Does not guarantee NUL-termination of s on some platforms (Microsoft, Sun)MacOS X: NUL-termination guaranteedCheck with "man sprintf"int vsprintf(char * str, const char * format, va_list ap);Buffer "str" can be overflowed1733Gets, fgetschar * gets(char *str);Buffer "str" can be overflowedchar * fgets(char *str, int size, FILE *stream);Buffer "str" is not NUL-terminated if an I/O error occursIf an error occurs, returns NULLIf end-of-file occurs before any characters are read, returns NULL also (and buffer is unchanged)Callers must use feof(3) and ferror(3) to determine which occurred.34ConclusionBuffer sizes should be passed as a parameter with every pointerApplies to other buffer manipulations besides stringsNeed simple truncation detectionCalls to watch out forHundreds of such callsUse static analysis to find these problemsCareful code review is necessaryInstead of: Use: gets(buf) fgets(buf, size, stdin) strcpy(dst, src) strncpy(dst, src, n) strcat(dst, src) strncat(dst, src, n) sprintf(buf, fmt, a1,) snprintf(buf, fmt, a1, n1,)(where available) *scanf() Your own parsing 1835Preventing Buffer Overflows Without ProgrammingIdea: make the heap and stack non-executableBecause many buffer overflow attacks aim at executing code in the data that overflowed the bufferDoes not prevent "return into libc" overflow attacksBecause the return address of the function on the stack points to a standard "C" function (e.g., "system"), this attack does not execute code on the stacke.g., ExecShield for Fedora Linux (used to be RedHat Linux)36Canaries on a Stack (Crispin Cowan)Add a few bytes containing special values between variables on the stack and the return address. Before the function returns, check that the values are intact.If not, there has been a buffer overflow!Terminate programIf the goal was a Denial-of-Service, then it still happens, but at least the machine is not compromisedIf the canary can be read by an attacker, then a buffer overflow exploit can be made to rewrite it1937StackGuard detectAdd Canary Word next to return address Observation (true only for buffer o.f.)Return address is unaltered IFF canary word is unaltered (?) Guessing the Canary ? Randomize38StackGuard - detectWhen compiling the function, it adds prologue and epilogueBefore execution of function, push word canary into canary vector in addition to the stackAfter execution, before returning from function check whether canary is intactFunction returns ONLY if canary is intact2039StackGuard PreventWhile function is active, make the return address read-onlyattacker cannot change the return addressany attempt will be detectedUse a library called MemGuardmark virtual memory pages as read-only and trap every writelegitimate writes to stack causes trapPerformance penalty 40Canary ImplementationsStackGuardStack-Smashing Protector (SSP)gcc modificationUsed in OpenBSDhttp://www.trl.ibm.com/projects/security/ssp/Windows: /GS option for Visual C++ .NETThese can be useful when testing too!2141StackGuard BypassGuarding a stack is not the answer, as B.O. is not a stack problem but a pointer problem (controlling a pointer the instruction pointer in this case-)Consider a function with several local variables, some of which are pointers: if we overflow B, we can overwrite pointer A. If this is a function pointer, it will be called, then pointing to our codeArgumentsReturn AddresscanaryLocVar: pointer ALocVar: buffer BLocVar: buffer A42StackGuard Bypass (cont.)The return address can be overwritten without touching the canary value (trampolining)Another possibility is to modify pointer A to point to a structure that holds function pointers, modifying an address there; point one of these back to buffer. If function gets called and buffer still around, control achieved.ArgumentsReturn AddressLocVar: buffer ALocVar: pointer ALocVar: buffer Bcanary2243Protection Using Virtual Memory PagesPage: A unit of virtual memoryPOSIX systems have three permissions for each page.PROT_READPROT_WRITEPROT_EXECIdea: manipulate and enforce these permissions correctly to defend against buffer overflowsMake injected code non-executable44Windows Execution Protection"NX" (No Execute)Windows XP service pack 2 featureSomewhat similar to POSIX permissionsRequires processor supportAMD64Intel Itanium (family of processors 64 bit)2345Arithmetic Issues:In mathematics, integers form an infinite set, but in systems they are binary strings of fixed length (precision), so a finite set. Familiar rules of arithmetic do not apply.In unsigned 8-bit integer arithmetic1. 255+1= 0, 2. 16 X 17=16 and 3. 0-1=255In particular, a negative value (as in 3.) can be interpreted as a large positive one46Example (using 1.)Consider the following code snippet that copies two character strings into a buffer and checks the combined length so they fitchar buf [128]combine(char *s1, size_t len1, char *s2,size_t len2) {if (len1+len2+1 2447Example (using 3.)Consider the following code snippet int main(int argc, char* argv[]){ char _t[10]char p[]=xxxxxxx; char k[]=zzzz; strncpy(_t, p, sizeof(_t);strncat(_t, k, sizeof(_t) strlen(_t)-1);return 0; }After execution, the resulting string in _t is xxxxxxxzz;Now if we supply 10 chars in p (xxxxxxxxxx), then sizeof(_t)and strlen(_t) are equal and the third argument is -1. Since strncat expects unsigned as third argument, it is interpreted as 0xFFFFFFFF and therefore the strcat is unbounded and the buffer overrun again. 48Important LessonDeclare all integers as unsigned integers, unless negative ones are really needed. While measuring size of objects, negative ones are not needed. If compiler flags signed-unsigned mismatch, check if both representations are needed; if so, care needed to the checks implemented.Most arithmetic bugs are caused by type mismatch2549Buffer Overflow in Java?Not really, since Java has a type-safe memory model, and falling off the end of an object is not possible. Exploits against Java-based systems are typically language-based (type confusion) attacks and trust exploits (code signing errors) Problem overflow typically occur in supporting code external to the JVM: use, by Java-based services, of components and services written in weakly typed languages like C and C++Java supports loading of DLLs and code libraries, so that exported functions can be used directly50example Public class MyJavaPacketEngine extends Thread{public MyJavaPacketEngine (){}static{System.loadLibrary(packet_driver32);} }Now calls can be made directly to the DLL.For examplewsprintf(lpAdapter->SymbolicLink, TEXT(\\\\.\\%s%s), DOSNAMEPREFIX, p_AdapterName);Assigns the binding string to an unterminated string buffer


View more >