10

I want to comprehend the exact difference between these two types of attack. From what I have read:

Buffer Overflow: It overwrites the ret address on the stack to point to another section of the code where the malicious code is inserted. So effectively - here we need to modify the source code of the program to actually carry out the attack.

Return to Libc- Here instead of modifying the source code, run time function calls provided by the C library are used (to say open up a shell). Here the parameters used for the function call are also passed in the overwriting buffer, ending up after the ret part of the stack.

Is the above an accurate description ?

And another related question - would it be possible to have a buffer overflow attack without actually modifying the source code of the original program? Probably if we write a new program and allow that to modify certain sections of memory (which is the new ret address in the corrupted stack of the original program). Then again, I think this might not be possible due to memory protection offered between processes in the kernel.

Ethan Heilman
  • 16,347
  • 11
  • 61
  • 88
Hari
  • 5,057
  • 9
  • 41
  • 51

4 Answers4

12

In the classic buffer overflow exploit, the stack buffer being overflowed was filled with both the machine code to be executed (called the shellcode, because it typically invoked a shell process) and the new return address. The new return address would be crafted to point back within the overflowed stack buffer itself. Obviously, this requires knowing or guessing the address of that stack buffer in the attacked process.

In those days, the memory layout of processes was typically highly deterministic - the location of the stack buffer could usually be predicted quite well by the attacker (particularly if they knew exactly which version of the target software was being attacked). To improve the chances of success when there was some guesswork involved, the active shellcode would often be preceeded by a large quantity of executable machine code that performed no useful operation - called a "NOP sled" or "NOP slide", where "NOP" is the typical name for a machine code instruction that performs "No Operation". Returning to a point anywhere in the the NOP sled would have the desired effect.

A "return-to-libc" exploit, on the other hand, does not cause the hijacked process to return directly to the shellcode. Instead, it causes the process to return, one by one, to the start of a chain of library functions. These library functions might directly perform the operations the attacker wants, but more commonly they will be used to indirectly execute the attacker's shellcode.

caf
  • 233,326
  • 40
  • 323
  • 462
4

The part of overwriting a ret address is shared between both attacks. As the above reply indicates, you used to simply return into assembly code you had written. This assembly code would then, say, spawn a root user shell.

In neither attack you would 'overwrite' the source code. Viewing it from an assembly perspective, the source code is in the .text segment and has always been (or at least all the time I know of) write-protected. What you used to take advantage of was to write code that you had priorly assembled into memory segments, and then jump to this code. The code was most typically located in or near the 'stack segment', and by overflowing whatever you choose to overlow, you'd re-direct traffic from the ret address (say) there. Other attack venues included an environmental variable you had priorly created and filled with your shellcode; or also the heap, or function pointers, or the PLT. The code so inserted would usually use the system() call to execute what was desired - but for this, you need to be able to 'execute' on memory areas (or have the relocation entries you intend to use being declared writable).

The difference between the two attacks is that once memory had been made largely non-executable, attack type a (stack overflow) was pretty much screwed. An attempt to bypass this type of attack was then, as you write, to instead access shared library functions directly - aka, you had no longer need to write your code first into the stack segment or elsewhere and execute it there. However, I believe libc type attacks are also largely patched by now; I don't have the details handy; and maybe I'm wrong.

If you'd like to see how any of these attacks are being thwarted these days, or at least read about some key ideas, google 'Smashing the Stack in 2011' (or 2010) to get started.

gnometorule
  • 2,151
  • 2
  • 20
  • 29
  • Could you explain what you mean by - making memory executable (or non-executable). And the other doubt (part of my original question) is - I believe a C code would not have access to all parts of the main memory. So how is it ascertained where the malicious assembly code is placed in memory. Because I think for most regions of memory - it would just flag a segmentation error when returning there. – Hari Sep 05 '11 at 02:39
  • The core of exploit code you insert was typically using and exec() family style function. This means, by feeding it the proper parameters, you could have it, say, spawn a /bin/sh shell as the root user. This is *executing* a program, and you can't anymore. There are many different security measures, but this one would be described, eg, in – gnometorule Sep 05 '11 at 05:01
  • Sorry for the fractured commment - something went bad in my browser. If you run Ubuntu, say, this here shows what is currently implemented:https://wiki.ubuntu.com/Security/Features – gnometorule Sep 05 '11 at 05:03
  • You write you 'doubt c code has access to all parts of memory.' That is true as well, but only part of the whole package of measures you face. One example for this is (I"m working under Ubuntu, but it will be similar elsewhere) the setting of a canary value (you can google that too). – gnometorule Sep 05 '11 at 05:13
  • If you use gcc, compile once with -fno-stack-protector, once w/o, and have a look at the disassembly. You'll notice a reference to gs:14 near the ebp, which sets the canary value, and which you cannot read from, nor write to - at least not with any trick I know. The flagging of segmentation errors, you are right, is the general response to the detection of an exploit attempt. However, this does not mean that you necessarily had an attempt to overwrite a return address, although it usually does. – gnometorule Sep 05 '11 at 05:14
  • If you are asking how people still hack these days (where the heck do they place code to make this still work), I do not know. As I said, even libc attacks are I think now no longer successful - at least not the first generation of them (as I said above, those were different as the execution of the program was in a shared library, not on the stack). – gnometorule Sep 05 '11 at 05:14
4

I would say buffer overflow is a class of programming error and return to libc is a exploitation technique. It is best not to mix the concepts together.

For example, you can use return to libc to exploit a buffer overflow vulnerability. Or you can use other techniques such as return to .text, or return to shellcode. Conversely, you can also use return to libc to exploit other bugs such as format string too.

Nam Nguyen
  • 1,765
  • 9
  • 13
0

Well actually, in a bufferoverflow attack, you insert the malicious code while overriding the ret pointer. You do not need to modify anything for this, so as a conclusion I cannot see the difference between both attacks mentioned.

For Example:

char* str[5];
cin << str;

This is code which can be exploided, if a user inserts a string larger than 5 chars, everything on the stack which follows will be overwritten. And due to the fact that the ret-ptr is "lower" on the stack, you can override it, if you get the distance right. Your intention while overriding is to let it point at the begining of your input, in which you inserted malicious (assembler) code, which would be executed as soon as the ret-ptr is called and the "jump" executed.

Sim
  • 4,199
  • 4
  • 39
  • 77