Readme for Smashing the Stack 101 ================================= By j00nix The method ========== The method of exploiting stack buffer overflows can be described in 3 steps. 1. Finding the buffer size from either source code or disassembly. 2. Forcing a core dump from a buffer overflow. 3. Grabbing the stack pointer (ESP) register after the overflow. The buffer size from the source code should be ok, but should be checked with the assembly code, just to make sure any realignments from the compiler is not severe. If you are not good with assembly, just use the the buffer size from the source code. Methods for tackling alignment problems are discussed below. Then, proceed to alter the exploit (smash.c) so that it will force a core dump. Firstly, you must check to see if your shell is allowing core dumps, i.e. "ulimit -c" under bash. Set the ulimit to be a high value. Now there are two ways to do this: you could attempt to overwrite the saved EIP with a bogus address (as shown in smash 101), or you could increase the buffer size so that it overflows the buffer with NOPs (0x90) (as shown in smash 103). In some circumstances either method may not work. So try the other method or both methods combined until it works. After the core has been dumped, you run "gdb file core" to examine it. Type "info reg" at the prompt and look for the stack pointer's value. This will be ESP on intel x86 systems. Put this EXACT value into the base_address inside the exploit (smash.c). Compile and run the exploit. It should work instantly, unless you have alignment problems. This is discussed below. Alignment problems =================== From a mathematical point of view, unless the stack buffer has been allocated to the program with random spaces in between, it should be possible to determine not only the exact buffer address, but also to overwrite the EIP with the exact value of the shellcode. There are 3 values that need to be noticed in the exploit for alignment problems. 1) BUFFERSIZE = size of the overflowable buffer. 2) offset_multiplier = this determines the "buffer" address from the base address. 3) number of loops for insertion = 4 = this determines how many times we try and guess where the saved EIP is on the stack. Although 2 should be enough, the default used is 4 in case the CPU does something tricky. We should not have to change this value. (Actually, if everything goes as planned, the offset_multiplier would never have to be changed. Anything other than '2' means we are fudging our results because we cannot locate the buffer. This is similar to aleph1's method with the 'offset' value.) First problem -------------- In our suid program, we have a function: void func() { char buf[255]; // non-aligned buffer. // our compiler aligns this to 256 bytes. } If we set BUFFERSIZE to 255, we will hit the saved EIPs wrongly because they won't be aligned. To fix this, we know that each EIP is one word long, and one word is 32-bits (4 bytes) on Linux. This means we only need to alter the BUFFERSIZE up to 4 bytes to fix the EIP alignment problem. However, there are compilers that may also insert additional space between the saved EBP/EIP and this buffer. (Perhaps as a guard like stackguard with canary values or whatever.) We will overwrite them by increasing our number of loops for insertion INSTEAD of changing the BUFFERSIZE. This allows us to keep within word alignment whilst breaking through any extra compiler stack allocation. (It should be noted that to defeat stackguard you have to set the proper canary values as well.) Here's how to do this in simple steps: 1) Find out how much "extra space" your compiler has allocated. The easiest way to do this is to sum up all the buffers and variables allocated on the stack, and subtract the amount with the allocated amount by looking through the disassembly (gdb->disas func). The result will tell you how much extra space is allocated. If you cannot determine the "extra space" just increase the number of loops with an estimate. As long as it loops enough times, it will overwrite the EIP. Each loop uses 4 bytes, so if you can determine the "extra space", the amount of loops is found by dividing the "extra space" by 4. Make sure that the number of loops doesn't exceed the highest address of the stack! Otherwise you may cause a segfault. 2) The amount of BUFFERSIZE adjustment is the actual buffer's size MOD 4. (MOD is the remainder of the division, i.e. a mod b is the remainder of a / b). If you can't work this out, just guess (you only have 4 possibilities). When you alter BUFFERSIZE, do it like this: #define BUFFERSIZE 256-1 and NOT like this #define BUFFERSIZE 256+1 i.e. Don't add to the buffersize. Only subtract from it. This is because we let out loop handle and overwrite any space still within our buffer and any "extra space" that the compiler might use, so that it eventually hits the saved EIP. Second problem -------------- void func() { char trash1[80]; char trash2[25]; char buf[255]; char trash3[30]; } This is just like the "extra space" problem with the compiler, but this time we know the exact amount of the extra space, and because they are the program's variables (not stackguard's canary values) we can assume that they are part of a larger buffer. Our buffer is still 'buf'. But this time, it has two buffers above it (in the stack) and one buffer below it. Any variables below 'buf' can be ignored (i.e. trash3). We sum the size of our total buffer space to overflow as buf+trash2+trash1 = 255 + 25 + 80. We then adjust that size for 32-bit alignment as before. Step by step method: 1) Do as previous problem and calculate "extra space" from the compiler, and re-adjust the number of loops as appropriate. 2) Calculate the sum of the buffers (or variables) above and including the target buffer. This is the BUFFERSIZE. 3) Adjust BUFFERSIZE for alignment, if necessary, by subtracting up to 3 bytes. Brute force this value if you have to. Conclusion =========== The main reason why I wanted to discuss buffer overflows was that the example and reasons given in Aleph1's classic text "Smashing the stack for fun and profit" was quite fuzzy on the details. Mathematics and logic would tell us that it should be possible to find exact locations for the base/buffer address as well as overwriting the saved EIP exactly, given that there are no other external factors that we do not know (such as randomness). Yet, we still have to accept that we may not be able to find the exact values. This is why we still use NOPs in the buffer. (Although any other opcode that doesn't alter program path or stack/heap memory could do just as well). Finally, if you have reached this paragraph without needing to change the offset_multiplier then you have finally succeeded in pulling off an exploit with "exact" values.