Vulnerability Development mailing list archives

Re: No-Exec Stack Smashing 101


From: huuskone () CC HELSINKI FI (Taneli Huuskonen)
Date: Thu, 27 Apr 2000 00:00:47 +0300


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

"Granquist, Lamont" <lamont () ICOPYRIGHT COM> wrote:

Okay, lets say that you've got:

1.  non-exec stack
2.  libc remapped to location with 0x00 in it
3.  statically linked executable, so no PLT functions

And assume the bug is a simple buffer overflow in a string function which
terminates on a 0x00 (i.e. ignore for the moment ways around a 0x00
"canary")

You mean the possibility of overflowing the same buffer several times
within the same function and such?

How can you get around that?  Is there a more general way around non-exec
stacks than return-into-PLT exploits?

Well, the following possibilities spring to mind:

1)  Certainly the simplest case is when the programme calls system()
itself  -  just "return" to the call.

2)  Call some other legitimate subroutine with rogue parameters.  For
instance, call an initialization routine to read a bogus configuration
file.

3)  Use some other part of memory that is both writable and executable.
For instance, a user-supplied string might be copied to a malloc()'ed or
static buffer at a predictable address, which might be executable.

4)  Use a subroutine within the accessible address range to shift some
values around on the stack so that after two levels of return, the libc
routine is called with user-supplied arguments.

This needs a bit more explanation.  Assume that there is a subroutine
that copies a value from, say, BP+8 to BP-0x14, (that'd be a parameter
to a local variable) and doesn't do anything much after that before
returning.  Let the address of the subroutine, after the stack
manipulation on entry, be 0x88888888, and let the value of BP be
0xCCCCCCCC when the buffer overflow occurs.  Let the address of the
system() function be 0x00102030.

The overflowed buffer could contain the following data at given
addresses (0xDEADBEEF denotes unimportant values):

0xCCCCCC80: the string "echo foo::0:0::/:/bin/sh >>/etc/passwd;"

0xCCCCCCCC: 0xCCCCCCEC - the value for BP for the sub at 0x88888888
0xCCCCCCD0: 0x88888888 - "return" address
0xCCCCCCD4: 0xDEADBEEF - value for BP when system() is called
0xCCCCCCD8: 0xDEADBEEF - will be overwritten by 0x00102030
0xCCCCCCDC: 0xDEADBEEF - address that system() returns to
0xCCCCCCE0: 0xCCCCCC80 - the parameter for system()
0xCCCCCCE4: 0xDEADBEEF
0xCCCCCCE8: 0xDEADBEEF
0xCCCCCCEC: 0xCCCCCCD4 - value for BP in the second end-of-sub
0xCCCCCCF0: 0x88888888 - any end-of-subroutine would do
0xCCCCCCF4: 0x00102030 - possible on a little-endian machine

Now, when the subroutine containing the overflow terminates, the value
of BP (0xCCCCCCCC) is moved to SP.  Then BP gets the new value
0xCCCCCCEC from stack, and control returns to 0x88888888.  This address
is in the middle of a subroutine, so BP is not pushed onto the stack and
replaced by the value of SP.  The subroutine copies the value 0x00102030
from 0xCCCCCCF4 (BP+8) to 0xCCCCCCD8 (BP-0x14), moves BP to SP, pops
0xCCCCCCD4 into BP, and "returns" to 0x88888888.  An irrelevant value is
copied from 0xCCCCCCDC (BP+8) to 0xCCCCCCC0 (BP-0x14), BP is moved to
SP, an irrelevant value for BP is popped from the stack (location
0xCCCCCCD4), and the subroutine "returns" to the address 0x00102030,
that is, to the system() library function.  The value of SP is now
0xCCCCCCDC.  At that address, system() expects to find a return address,
followed by the address of the parameter string.  The system() function
passes the string at 0xCCCCCC80 to the shell, then returns to a bogus
address and crashes in this example.

5)  Find a "jump to register" opcode somewhere, set up the register
suitably, then "return" to the address of the register jump.  For
instance, if the subroutine containing the buffer overflow also parses a
decimal integer and leaves the result in DX before returning, you could
hunt for an opcode for "jump dx", including the value 1056816
(0x00102030) in the input and returning to the jump instruction.  The
two-byte opcode (or equivalent) is reasonably likely to exist purely by
chance in most programmes except for quite small ones.

6)  Ditto, but with a "syscall" opcode.  I don't know how difficult this
type of exploit would be in practice, but it's theoretically quite
possible, at least in combination with (2) above.

Of course, buffer overflows can be exploited in ways that don't affect
the path of execution at all  -  directing some output to the wrong file
can be effective enough, for example.

Taneli Huuskonen

-----BEGIN PGP SIGNATURE-----
Version: PGPfreeware 5.0i for non-commercial use
Charset: noconv

iQA/AwUBOQdYTF+t0CYLfLaVEQL/xACg0IsaJ0LAmG4PkvjCI7gz21x6LysAn1qu
Ecc2STwA6ZHTRHfeSUjEoZPu
=9MPO
-----END PGP SIGNATURE-----

--
I don't   | All messages will be PGP signed,  | Fight for your right to
speak for | encrypted mail preferred.  Keys:  | use sealed envelopes.
the Uni.  | http://www.helsinki.fi/~huuskone/ | http://www.gilc.org/



Current thread: