#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* TODO add fail fast mode TODO add PE32+ support TODO verify the entry point RVA and content. */ typedef struct { const char *data; guint32 size; GSList *errors; int valid; guint32 data_dir_count; } VerifyContext; #define ADD_VERIFY_INFO(__ctx, __msg, __status, __exception) \ do { \ MonoVerifyInfoExtended *vinfo = g_new (MonoVerifyInfoExtended, 1); \ vinfo->info.status = __status; \ vinfo->info.message = ( __msg); \ vinfo->exception_type = (__exception); \ (__ctx)->errors = g_slist_prepend ((__ctx)->errors, vinfo); \ } while (0) #define ADD_ERROR(__ctx, __msg) \ do { \ ADD_VERIFY_INFO(__ctx, __msg, MONO_VERIFY_ERROR, MONO_EXCEPTION_INVALID_PROGRAM); \ (__ctx)->valid = 0; \ } while (0) #define CHECK_STATE() do { if (!ctx.valid) goto cleanup; } while (0) static guint32 pe_signature_offset (VerifyContext *ctx) { return read32 (ctx->data + 0x3c); } static void verify_msdos_header (VerifyContext *ctx) { guint32 lfanew; if (ctx->size < 128) { ADD_ERROR (ctx, g_strdup ("Not enough space for the MS-DOS header")); return; } if (ctx->data [0] != 0x4d || ctx->data [1] != 0x5a) ADD_ERROR (ctx, g_strdup ("Invalid MS-DOS watermark")); lfanew = pe_signature_offset (ctx); if (lfanew > ctx->size - 4) ADD_ERROR (ctx, g_strdup ("MS-DOS lfanew offset points to outside of the file")); } static void verify_pe_header (VerifyContext *ctx) { guint32 offset = pe_signature_offset (ctx); const char *pe_header = ctx->data + offset; if (pe_header [0] != 'P' || pe_header [1] != 'E' ||pe_header [2] != 0 ||pe_header [3] != 0) ADD_ERROR (ctx, g_strdup ("Invalid PE header watermark")); pe_header += 4; offset += 4; if (offset > ctx->size - 20) ADD_ERROR (ctx, g_strdup ("File with truncated pe header")); if (read16 (pe_header) != 0x14c) ADD_ERROR (ctx, g_strdup ("Invalid PE header Machine value")); } static void verify_pe_optional_header (VerifyContext *ctx) { guint32 offset = pe_signature_offset (ctx) + 4; guint32 header_size; const char *pe_header = ctx->data + offset; const char *pe_optional_header = pe_header + 20; header_size = read16 (pe_header + 16); offset += 20; if (header_size < 2) /*must be at least 2 or we won't be able to read magic*/ ADD_ERROR (ctx, g_strdup ("Invalid PE optional header size")); if (offset > ctx->size - header_size || header_size > ctx->size) { ADD_ERROR (ctx, g_strdup ("Invalid PE optional header size")); return; } if (read16 (pe_optional_header) == 0x10b) { if (header_size != 224) ADD_ERROR (ctx, g_strdup_printf ("Invalid optional header size %d", header_size)); if (read32 (pe_optional_header + 28) != 0x400000) ADD_ERROR (ctx, g_strdup_printf ("Invalid Image base %x", read32 (pe_optional_header + 28))); if (read32 (pe_optional_header + 32) != 0x2000) ADD_ERROR (ctx, g_strdup_printf ("Invalid Section Aligmment %x", read32 (pe_optional_header + 32))); if (read32 (pe_optional_header + 36) != 0x200) ADD_ERROR (ctx, g_strdup_printf ("Invalid file Aligmment %x", read32 (pe_optional_header + 36))); /* All the junk in the middle is irrelevant, specially for mono. */ ctx->data_dir_count = read32 (pe_optional_header + 92); if (ctx->data_dir_count > 0x10) ADD_ERROR (ctx, g_strdup_printf ("Too many data directories %x", ctx->data_dir_count)); } else { if (read16 (pe_optional_header) == 0x20B) ADD_ERROR (ctx, g_strdup ("Metadata verifier doesn't handle PE32+")); else ADD_ERROR (ctx, g_strdup_printf ("Invalid optional header magic %d", read16 (pe_optional_header))); } } GSList* mono_image_verify (const char *data, guint32 size) { VerifyContext ctx; memset (&ctx, 0, sizeof (VerifyContext)); ctx.data = data; ctx.size = size; ctx.valid = 1; verify_msdos_header (&ctx); CHECK_STATE(); verify_pe_header (&ctx); CHECK_STATE(); verify_pe_optional_header (&ctx); CHECK_STATE(); cleanup: return ctx.errors; }