memory-safety (week 2) Flashcards
what does memory safety refer to?
ensuring the integrity of a program’s data structures: preventing attackers from reading or writing to memory locations other than those intended by the programmer
why is C vulnerable to buffer overflow bugs?
because of the absence of automatic bounds-checking for array or pointer access
a buffer overflow bug is one where the programmer fails to perform adequate bounds checks, triggering an out-of-bounds memory access that writes beyond the bounds of some memory region
attackers can use these out-of-bounds memory accesses to corrupt the program’s intended behavior
provide an example as to why code like this is vulnerbale to an attack:
char buf[80]; int authenticated = 0; void vulnerable() { gets(buf); }
Imagine that elsewhere in the code, there is a login routine that sets the authenticated flag only if the user proves knowledge of the password. Unfortunately, the authenticated flag is stored in memory right after buf. If the attacker can write 81 bytes of data to buf (with the 81st byte set to a non-zero value), then this will set the authenticated flag to true, and the attacker will gain access. The program above allows that to happen, because
the gets function does no bounds-checking: it will write as much data to buf as is supplied to it. In other words, the code above is vulnerable: an attacker who can control the input to the program can bypass the password checks.
why is this variation even more dangerous?
char buf[80]; int (*fnptr)(); void vulnerable() { gets(buf); }
The function pointer fnptr is invoked elsewhere in the program (not shown). This enables a more serious attack: the attacker can overwrite fnptr with any address of their choosing, redirecting program execution to some other memory location. A crafty attacker could supply an input that consists of malicious machine instructions, followed by a few bytes that overwrite
fnptr with some address A. When fnptr is next invoked, the flow of control is redirected to address A. Notice that in this attack, the attacker can choose the address A however they like—so, for instance, they can choose to overwrite fnptr with an address where the malicious machine instructions will be stored (e.g., the address &buf[0]). This is a malicious code injection attack. Of course, many variations on this attack are possible: for instance, the attacker could arrange to store the malicious code anywhere else (e.g., in some other input buffer), rather than in buf, and redirect execution to that other location.
if the web server is running as root, once the attacker seizes control, what is the attacker capable of doing?
anything that root can do (even leave a backdoor that allows them to log in as root later)
what is a stack smashing attack?
if the input to a function is too long to fit in the allocate space, the code will write past teh end of buf and the saved SP and return address will be overwritten
how can stack smashing be used for malicious code injection?
void vulnerable() { char buf[80]; gets(buf); }
- the attacker arranges to infiltrate a malicious code sequence somewhere int he program’s address space, at a known address
- the attacker provides a carefully chosen 88 byte input, where the last four bytes hold the address of the malicious code
- when vulnerable() returns, the CPU will retrieve the return address stored on the stack and transfer control to that address, handing control over to the attacker’s malicious code
modern complications for buffer overflow attacks:
- malicious code is stored at an unknown location
- buffer is stored on heap instead of on stack
- attack can only overflow the buffer by a single byte
- characters that can be written to the buffer are limited
- there is no way to introduce any malicious code into the program’s address space
what is one way to avoid buffer overflows in your code?
one way is to check that there is sufficient space for what you will write before performing the write
explain how format string vulnerabilities happen
void vulnerable() { char buf[80]; if (fgets(buf, sizeof buf, stdin) == NULL) return; printf(buf); }
the last line should be printf(“%s”, buf)
if buf contains any % characters, printf() will look for non-existent arguments, and may crash or core-dump the program trying to chase missing pointers
- printf, sprintf, snprintf, vfprintf, syslog
- variable number of arguments, one of which is “the format string”
- an attacker that supplies format string can change function’s behavior
if the attacker can see what is printed in a format string vulnerability, what are three attacks that could be mounted:
- the attacker can learn the contents of the function’s stack frame (supplying the string “%x:%x” reverals the first two words of stack memory”)
- the attacker can learn the contents of any other part of memory as well (Supplying the string “%s” treats the next word of stack memory as an address, and prints the string found at that address. Supplying the string “%x:%s” treats the next word of stack memory as an address, the word after that as an address, and prints what is found at that string. To read the contents of memory starting at a particular address, the attacker can find a nearby place on the stack where that address is stored, and then
supply just enough %x’s to walk to this place followed by a %s. Many clever tricks are possible, and the details are not terribly important for our purposes.) Thus, an attacker can exploit a format string vulnerability to learn passwords, cryptographic keys, or other secrets stored in the victim’s address space. - the attacker can write any value to any address in the victim’s memory; this could be used for malicious code injection
what should we assume if our code has a string format bug?
that the attacker can learn all secrets stored in memory, and assume that the attacker can take control of your program
What’s wrong with this code?
char buf[80]; void vulnerable() { int len = read_int_from_network(); char *p = read_string_from_network(); if (len > 80) { error("length too large: bad dog, no cookie for you!"); return; } memcpy(buf, p, len); } Here’s a hint. The prototype for memcpy() is: void *memcpy(void *dest, const void *src, size_t n); And the definition of size_t is: typedef unsigned int size_t;
If the attacker provides a negative value for len, the if statement
won’t notice anything wrong, and memcpy() will be executed with a negative third argument. C will cast this negative value to an unsigned int and it will become a very large positive integer. Thus memcpy() will copy a huge amount of memory into buf, overflowing the buffer.
what are some other examples of memory safety violations?
using a dangling pointer: a pointer into a memory region that has been freed and is no longer valid
double-free bugs: where a dynamically allocated object is explicitly freed multiple times
why is a double free bug so bad?
Double free errors occur when free() is called more than once with the same memory address as an argument.
Calling free() twice on the same value can lead to memory leak. When a program calls free() twice with the same argument, the program’s memory management data structures become corrupted and could allow a malicious user to write values in arbitrary memory spaces. This corruption can cause the program to crash or, in some circumstances, alter the execution flow. By overwriting particular registers or memory spaces, an attacker can trick the program into executing code of their own choosing, often resulting in an interactive shell with elevated permissions.
When a buffer is free()‘d, a linked list of free buffers is read to rearrange and combine the chunks of free memory (to be able to allocate larger buffers in the future). These chunks are laid out in a double linked list which points to previous and next chunks. Unlinking an unused buffer (which is what happens when free() is called) could allow an attacker to write arbitrary values in memory; essentially overwriting valuable registers, calling shellcode from its own buffer.