Software Security Return to Libc and ROP Jan Nordholz Prof. Jean-Pierre Seifert Security in Telecommunications TU Berlin SoSe 2014 jan (sect) Software Security SoSe 2014 1 / 13
Recap: Overflow Bugs Ingredients: fixed-size buffer on the stack missing bounds check while writing to buffer executable stack Recipe: overflow buffer overwrite return pointer beyond buffer return into attack code stored in buffer wreak havoc jan (sect) Software Security SoSe 2014 2 / 13
XD / NX Recap: processor’s paging mechanism translates virtual to physical addresses. Translation entries also contain flags, e. g. permission bits. Historically: only one bit for writability. R and X always granted. Around 2000: AMD64 / Intel PAE introduce the NX / XD bit (respectively). NX/XD: marks a page as non-executable. ⇒ Non-code areas can now be made non-executable! jan (sect) Software Security SoSe 2014 3 / 13
Back to Overflow Bugs Ingredients: fixed-size buffer on the stack missing bounds check while writing to buffer NON-executable stack Recipe: overflow buffer overwrite return pointer beyond buffer try to return into attack code stored in buffer SEGMENTATION FAULT jan (sect) Software Security SoSe 2014 4 / 13
Options for placing attack code Placing attack code into the overflowed stack buffer: hopeless. Stuffing the code into the environment before launching the vulnerable program: also on the stack, so hopeless. Heap, other global variables. . . : non-executable, so hopeless, too. Areas that are executable cannot be written to. ⇒ The only natural conclusion: Use code that’s already there! jan (sect) Software Security SoSe 2014 5 / 13
Return to Libc Assumption: we know the address of system() . . . . . . and we have a stack buffer we can overflow, as before. Idea: instead of call ing the function (as regular code would), we ret into it. Remember: call itself pushes the return pointer the called function pushes the caller’s frame pointer onto the stack arguments to the called function start at %ebp + 8 On the other hand: ret pops one word from the stack we have to take this into account when preparing arguments jan (sect) Software Security SoSe 2014 6 / 13
Return to Libc Stack pointer will be at ”filler” when we enter system() Function will mistake this as its return address. . . . . . push the caller’s frame pointer below it. . . . . . and expect arguments above it. (Cf. picture.) ⇒ If you know the address of system() : easy as pie. You might not always know (see next week, keyword: ASLR). Or your libc may come with system() disabled. jan (sect) Software Security SoSe 2014 7 / 13
Interlude: Code at the Machine Level Skeleton of a usual function: setup frame pointer push registers the function is going to use (i. e. clobber) make room for local variables (subtract from stack pointer) (function body) reclaim room restore register state possibly adjust/set return value unwind frame pointer, return Idea: return into the footer of a function, executing a few instructions of choice before hitting the inevitable return. ⇒ That next return is also controlled by us (we own the stack)! jan (sect) Software Security SoSe 2014 8 / 13
Return Oriented Programming (ROP) pop %ebx ret We target this code fragment with our first return. Pops the next value from the stack into EBX (which we control!). . . and returns again, once more to a value of our choice. ⇒ Technically equivalent to mov $CONSTANT, %ebx in ”normal” shellcode Find suitable fragments for all registers we want to set. . . daisy-chain them to form our exploit! We don’t care about other registers being clobbered. If a function epilogue we want to use adjusts the stack pointer, just include enough padding room into our ROP chain. jan (sect) Software Security SoSe 2014 9 / 13
Return Oriented Programming (ROP) Usual ROP exploits chain dozens of fragments (gadgets) together. Virtually every possible instruction has a ”translation” in ROP. Some might require more than one gadget, though. Stack space required usually extends way beyond the overflowed buffer (amount depends on the space efficiency of selected gadgets, i. e. the amount of stack pointer adjustment incurred) Number of available gadgets depends on size of executable and whether library load adresses are known jan (sect) Software Security SoSe 2014 10 / 13
Unaligned ROP Number of gadgets is even higher on architectures with variable-size instructions (x86). Consider: 8B5C2420 mov ebx,[esp+0x20] 8B742424 mov esi,[esp+0x24] 8B7C2428 mov edi,[esp+0x28] 83C42C add esp,byte +0x2c C3 ret There is no inherent notion of ”alignment”! What would happen if we ROP to the 5C instead of the 8B ? jan (sect) Software Security SoSe 2014 11 / 13
Unaligned ROP 8B5C2420 mov ebx,[esp+0x20] 8B742424 mov esi,[esp+0x24] 8B7C2428 mov edi,[esp+0x28] 83C42C add esp, +0x2c C3 ret 5C pop esp 2420 and al,0x20 8B742424 mov esi,[esp+0x24] 8B7C2428 mov edi,[esp+0x28] 83C42C add esp, +0x2c C3 ret We now have a pop esp , i. e. we could continue our ROP exploit from a different memory location (good if we’re running low on overflowable stack space) Note: x86 code stream reconverges pretty fast. ⇒ The already large number of gadgets increases again! jan (sect) Software Security SoSe 2014 12 / 13
Preventing ROP? ROP reuses ”valid code” — hard to detect once the return pointer is overwritten. While initially restricted to gadgets in the executable (fixed load address), attackers can use ROP to read GOT/PLT and learn resolved library symbols Defense mechanisms must trigger earlier, i. e. detect stack corruption before forged return pointer is ”accepted” ⇒ Next week: defenses! jan (sect) Software Security SoSe 2014 13 / 13
Recommend
More recommend