Skip site navigation (1)Skip section navigation (2)
Date:      10 Feb 1997 07:49:14 -0000
From:      tqbf@enteract.com
To:        roberto@keltia.freenix.fr, freebsd-security@freebsd.org
Subject:   Re: buffer overruns
Message-ID:  <19970210074914.22012.qmail@char-star.rdist.org>
In-Reply-To: <19970209231433.QS19404@keltia.freenix.fr>

next in thread | previous in thread | raw e-mail | index | archive | help
In article <19970209231433.QS19404@keltia.freenix.fr>, you wrote:
>If you put enough characters in $SOME_VARIABLE, you could trash, the return
>address that is usually just before the automatic variables...

Usually? =) You're overwriting (everyone seems to have a different term
for this) your call-stack-frame/procedure-activation-record, which is a
structure describing the routine's caller's state. One of the bits of
information in that state is the address to return to after the procedure
ends. What's a situation in which the information from the stack frame is
not adjacent to the procedure's automatic variables?

>So if you able to calculate how many bytes the automatic variables will
>take and put enough data in tmp to overrun all variables *and* the return
>address, you'll be able to change that return address, making it pointing

This is much simpler than you're making it sound.

I don't need to calculate anything, and, if I take the time to calculate
anything, it takes a trivial amount of data to accomplish this. First, if
I know roughly how large the buffer I'm overflowing is (by reading the
source, or by trial and error looking for segfaults), I can ensure the
stack frame is overwritten entirely with forged addresses simply by
filling the end of the buffer, along with a couple hundred bytes after the
buffer, with return addresses. 

I don't usually need to calculate where these return addresses point to.
If I'm exploiting a typical stack overflow, I already know that the data
I've spewed at the program is sitting somewhere in the stack segment,
which starts at a specific address regardless of the program. If I make
an offset from some sane base-stack-address value a configurable option in
my exploit, trial and error will eventually show me a good address to jump
to to take over the program.

Recall now that I'm almost always jumping to some address pointing into
the buffer that I've thrown at the program (this is where I usually have
the most control over what's in the memory space of the process I'm trying
to take over). Obviously, if I have to jump *exactly* to the first machine
instruction of my real exploit code (whatever is generating a shell for
me), I've got a real challenge - it'll take thousands of attempts to find
the right address without the ability to pull the program up in a
debugger.

However, I can maximize my acceptable margin of error by filling most of
my buffer up with valid instructions. If I hit *any* of these
instructions, the processor will simply execute the instruction, fetch the
next one, execute it, etc, etc, until eventually I've "slid" into my real
exploit code. If the buffer I'm overflowing is 2048 bytes long, I can fill
it with about 2000 1 byte null-ops, then 40 bytes of real exploit code
(execve()), and then enough return addresses to ensure that the stack
frame is trashed. I now have a 2000-byte margin of error.

Of course, if I'm in a situation with a limited amount of space in which
to inject my code, I can always pull the program up in a debugger, find
out *exactly* where the buffer I'm overflowing starts in memory, and
engineer a more careful exploit. Often, this isn't even necessary, as I've
got enough space in the stack to trample after a few procedures have been
called that I can simply fill the buffer entirely with return addresses,
and tack a few K worth of opcodes into the stack after the buffer. 

This technique (which, along with some other ingenious tricks, was first
published in the Linux i386 splitvt exploit) almost *always* works without
modification. In fact, because most programs don't use too much stack
space, if I have a valid stack address, more often than not, I'll be able
to get an exploit working without significant trial and error. crt0 worked
for me, by cutting and pasting the exploit code from lpr's card() routine,
on the first try.

Really, the only thing that varies between programs is the number of bytes
I have to pad the return addresses with, as they're often misaligned by a
byte or two. 

These methods are documented, exceedingly simple to execute once you're
familiar with them, and aren't time consuming. Stack overruns are, at this
point, trivial to exploit for most people with elementary C coding
ability.

>The easiest way to close all this bugs is to make the stack non executable
>(from a processor standpoint) but I'm not sure you can do it in Intel
>processors.

You'll have to make every other region of memory that a calling process
could potentially control non-executable as well. It's a gross assumption
to say that I, being the caller of any given program, only have influence
over the contents of that program's stack.

-- 
----------------
Thomas Ptacek at EnterAct, L.L.C., Chicago, IL [tqbf@enteract.com]
----------------
exit(main(kfp->kargc, argv, environ));




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?19970210074914.22012.qmail>