** Bug categories ====================== semantic bugs memory bugs uninitialized read dangling pointer buffer overflow stack buffer, heap buffer, global buffer overflow = read/write double free memory leak concurrency bugs Bugs cause crash wrong/missing functionality security attack This class shows how to launch attack using stack overflow bug, heap overflow bug, and concurrency bugs. ======================== * attack through stack overflow * ======================== The key of attack is to change the control flow lead the program to execute malicious code (injected by user) or execute existing code with malicious inputs (injected by user) How to inject code/data into normal program's program space? A: using buffer overflow. IF there is no bounds-checking, you can write whatever you want into the program space, UNTIL you reach the boundary of data segment and cause segmentation fault. How to control the control flow? A: leverage stack return address: when function A calls function B, the return address is stored on stack, right above B's stack frame. As long as we can overwrite the memory location where the return address is stored, hardware will automatically bring the program to a user-specified location. Stack structure * grow from high-address to low-address * call convention push inputs <-- done by caller push return address <--done by `CALL' instruction push ebp <-- done by callee `CALL' `RET': pop return address and jump to whatever stored there when stack buffer is overflowed, it will overflow towards the caller's stack, overwrite ebp, return address ... In order to launch attack: 0. figure out the binary of the code that you want the program to execute e.g., an execve("/bin/sh", ...) [shell gives you opportunity to execute whatevery you want with the program-user's priviledge) A couple of `tricks' in writing this binary (1) "/bin/sh" is a constent string. How to get the address? Its location is not known in advance Solution: put a call before it. CALL will push the address of the next instruction on stack! (Does this sound familiar? Very similar to Multics' trick to get link segment address actually :) (2) we cannot have "\0" in our binary, because many attacks are launched through strcpy strcat related string function. str operation usually would end at '\0', which would cause our code after '\0' be lost. Solution: replace constant 0 with xor eax, eax :) 1. use malicious code (usually a function call to execve("/bin/sh",..)) to overflow the buffer 2. (hopefully, the malicious shell code is laied on callee stack frame) overwrite the stack return address Question: how do I know where to jump to? We need to put an abosolute, instead of relative, address inside the stack return address location. How? We can only guess. Wrong guess would lead to segmentation fault (imagine we jump to the middle of an instruction) Solution: add many NOP before the malicious code. NOP is 1 byte and won't not hurt the execution effects. This way, jumping to one of the NOP would mean a successful attack! ========================== *Heap overflow attack* ===================== The target of heap overflow attack is also to overwrite the stack return address. How can we leverage heap overflow to overwrite stack? One naive solution is to overflow heap, keep overflowing until we reach stack. This is almost doom to fail, because the space between heap and stack is usually unmapped Solution (this is a long story): fact1: bookkeeping information for each heap object is stored on-site e.g., g=malloc(10); malloc actually allocate more than 10bytes to g. What you have in heap is: ---------------------------------------------------------- prev-object size (4 bytes) | ---------------------------------------------------------- current-object size | prev_in_use flag, etc. (4 bytes) | ---------------------------------------------------------- data | ... | ---------------------------------------------------------- When you free an object, what is on your heap is ---------------------------------------------------------- prev-object size (4 bytes) | ---------------------------------------------------------- current-object size | prev_in_use flag, etc. (4 bytes) | ---------------------------------------------------------- prev (previous freed object address) $ | ---------------------------------------------------------- next (next freed object address) * | ---------------------------------------------------------- ... | ---------------------------------------------------------- When we overflow a heap object (denoted as O1), we can write whatever we want to the object following us. (We can definitely put whatever code we want there, but that is not the key. The key is how can we overwrite to stack return address) What we will do is to overwrite the following object (denoted as O2) to make sure (1) O2->next's prev_in_use flag indicates O2 is a free object (2) put the starting address of your malicious code in O2->prev (marked by $) (3) put the stack return address location -8 into O2->next (marked by *) When we call free(O1) glibc will check whether the object following O1 is also free (glibc will find it to be free, because we set the bit to be so) then glibc will merge O1 with O2 as one big free object AND remove O2 from the doubled-link-list (this is the KEY!!) How to remove an object from doubled link list? You will execute something like this: O2->next->prev=O2->prev; (let O2's next object points to O2's previous, because O2 is no longer the previous object of its next object) This essentially is storing the value of O2->prev (the $ marked in the figure) to the memory address *+8 Since we have overwritten $ to be the starting point of my malicious code Since we have overwritten * to make it stack-return-address-location minus 8 Yeah, we succeed in overwriting stack return address!!! (Obviously, it is much harder to exploit heap overflow than stack overflow) ========================== How to fight against buffer overflow attack? 1. bug detection 2. put canary (between local variable and return address) to know whether overflow has happened 3. make stack/heap non-executable (Intel adopts NX bits for page; linux had patch of using NX bits even on hardware that does not support NX bit, by leveraging the NX bits in segment descriptor) 4. use address space layout randomization to make it hard to guess those powerful library function (prevent the attacker from executing existing code, e.g., system(xx), with malicious input)