diff --git a/docs/modules/dotnet.rst b/docs/modules/dotnet.rst index 3f44acdc..9c132a64 100644 --- a/docs/modules/dotnet.rst +++ b/docs/modules/dotnet.rst @@ -77,6 +77,111 @@ Reference *Example: dotnet.guids[0] == "99c08ffd-f378-a891-10ab-c02fe11be6ef"* +.. c:type:: number_of_classes + The number of classes in the file. + +.. c:type:: classes + An array of .NET classes stored in the metadata. Individual classes can be accessed + using the [] operator. Each class object contains the following attributes: + + .. c:member:: fullname + Class full name. + + .. c:member:: name + Class name. + + .. c:member:: namespace + Class namespace. + + .. c:member:: visibility + Class visibility specifier, options are: + + ``private`` + ``public`` + ``protected`` + ``internal`` + ``private protected`` + ``protected internal`` + + .. c:member:: type + Type of the object, options are: + + ``class`` + ``interface`` + + .. c:member:: abstract + Boolean representing if class is abstract. + + .. c:member:: sealed + Boolean representing if class is sealed. + + .. c:member:: number_of_generic_parameters + Number of generic parameters. + + .. c:member:: generic_parameters + A zero-based array of generic parameters name. Individual parameters can be accessed using the [] operator. + + .. c:member:: number_of_base_types + Number of the base types. + + .. c:member:: base_types + A zero-based array of base types name. Individual base types can be accessed using the [] operator. + + .. c:member:: number_of_methods + Number of the methods. + + .. c:member:: methods + A zero-based array of method objects. Individual methods can be accessed by + using the [] operator. Each object contains following attributes: + + .. c:member:: name + Method name. + + .. c:member:: visibility + Method visibility specifier, options are: + + ``private`` + ``public`` + ``protected`` + ``internal`` + ``private protected`` + ``protected internal`` + + .. c:member:: static + Boolean representing if method is static. + + .. c:member:: virtual + Boolean representing if method is virtual. + + .. c:member:: final + Boolean representing if method is final. + + .. c:member:: abstract + Boolean representing if method is abstract. + + .. c:member:: return_type + Method return type name. + + .. c:member:: number_of_parameters + Number of the method parameters. + + .. c:member:: parameters + A zero-based array of method parameters. Individual parameters can be accessed by using the [] operator. + + .. c:member:: name + Parameter name. + + .. c:member:: type + Parameter type. + + .. c:member:: number_of_generic_parameters + Number of the method generic parameters. + + .. c:member:: generic_parameters + A zero-based array of method generic parameters. Individual parameters can be accessed by using the [] operator. + + *Example: dotnet.classes[0].fullname == "Launcher.Program"* + .. c:type:: number_of_resources The number of resources in the .NET file. These are different from normal PE diff --git a/libyara/include/yara/dotnet.h b/libyara/include/yara/dotnet.h index f9e59487..8d66d33e 100644 --- a/libyara/include/yara/dotnet.h +++ b/libyara/include/yara/dotnet.h @@ -3,6 +3,7 @@ #include #include +#include #pragma pack(push, 1) @@ -72,6 +73,10 @@ typedef struct _TILDE_HEADER ULONGLONG Sorted; } TILDE_HEADER, *PTILDE_HEADER; +// flag in HeapSizes that denotes extra 4 bytes after Rows +#define HEAP_EXTRA_DATA 0x40 +#define SIG_FLAG_GENERIC 0x10 + // These are the bit positions in Valid which will be set if the table // exists. #define BIT_MODULE 0x00 @@ -129,16 +134,86 @@ typedef struct _TILDE_HEADER //#define BIT_IMPORTSCOPE 0x35 //#define BIT_STATEMACHINEMETHOD 0x36 -// -// Element types. Note this is not a complete list as we aren't parsing all of -// them. This only includes the ones we care about. -// ECMA-335 Section II.23.1.16 -// -#define ELEMENT_TYPE_STRING 0x0E - // The string length of a typelib attribute is at most 0xFF. #define MAX_TYPELIB_SIZE 0xFF +// Flags and Masks for .NET tables +#define TYPE_ATTR_CLASS_SEMANTIC_MASK 0x20 +#define TYPE_ATTR_CLASS 0x0 +#define TYPE_ATTR_INTERFACE 0x20 + +#define TYPE_ATTR_VISIBILITY_MASK 0x7 +#define TYPE_ATTR_NOT_PUBLIC 0x0 +#define TYPE_ATTR_PUBLIC 0x1 +#define TYPE_ATTR_NESTED_PUBLIC 0x2 +#define TYPE_ATTR_NESTED_PRIVATE 0x3 +#define TYPE_ATTR_NESTED_FAMILY 0x4 +#define TYPE_ATTR_NESTED_ASSEMBLY 0x5 +#define TYPE_ATTR_NESTED_FAM_AND_ASSEM 0x6 +#define TYPE_ATTR_NESTED_FAM_OR_ASSEM 0x7 + +#define TYPE_ATTR_ABSTRACT 0x80 +#define TYPE_ATTR_SEALED 0x100 + +#define METHOD_ATTR_ACCESS_MASK 0x7 +#define METHOD_ATTR_PRIVATE 0x1 +#define METHOD_ATTR_FAM_AND_ASSEM 0x2 +#define METHOD_ATTR_ASSEM 0x3 +#define METHOD_ATTR_FAMILY 0x4 +#define METHOD_ATTR_FAM_OR_ASSEM 0x5 +#define METHOD_ATTR_PUBLIC 0x6 + +#define METHOD_ATTR_STATIC 0x10 +#define METHOD_ATTR_FINAL 0x20 +#define METHOD_ATTR_VIRTUAL 0x40 +#define METHOD_ATTR_ABSTRACT 0x400 + +// Element types ECMA-335 Section II.23.1.16 +#define TYPE_END 0x0 +#define TYPE_VOID 0x1 +#define TYPE_BOOL 0x2 +#define TYPE_CHAR 0x3 +#define TYPE_I1 0x4 +#define TYPE_U1 0x5 +#define TYPE_I2 0x6 +#define TYPE_U2 0x7 +#define TYPE_I4 0x8 +#define TYPE_U4 0x9 +#define TYPE_I8 0xa +#define TYPE_U8 0xb +#define TYPE_R4 0xc +#define TYPE_R8 0xd +#define TYPE_STRING 0xe +#define TYPE_PTR 0xf +#define TYPE_BYREF 0x10 +#define TYPE_VALUETYPE 0x11 +#define TYPE_CLASS 0x12 +#define TYPE_VAR 0x13 +#define TYPE_ARRAY 0x14 +#define TYPE_GENERICINST 0x15 +#define TYPE_TYPEDREF 0x16 +#define TYPE_I 0x18 +#define TYPE_U 0x19 +#define TYPE_FNPTR 0x1b +#define TYPE_OBJECT 0x1c +#define TYPE_SZARRAY 0x1d +#define TYPE_MVAR 0x1e +#define TYPE_CMOD_REQD 0x1f +#define TYPE_CMOD_OPT 0x20 +#define TYPE_INTERNAL 0x21 +#define TYPE_MODIFIER 0x40 +#define TYPE_SENTINEL 0x41 +#define TYPE_PINNED 0x45 + +// Sane boundaries for invalid files +#define MAX_ARRAY_RANK 50 +#define MAX_PARAM_COUNT 2000 +#define MAX_GEN_PARAM_COUNT 1000 +#define MAX_METHOD_COUNT 20000 +#define MAX_STRING_LENGTH 10000 +// Sanity check for loops in type parser +#define MAX_TYPE_DEPTH 0x10 + // // Module table // ECMA-335 Section II.22.30 @@ -306,9 +381,84 @@ typedef struct _CONSTANT_TABLE } Value; } CONSTANT_TABLE, *PCONSTANT_TABLE; +// ECMA 335 - II.22.37 +typedef struct _TYPEDEF_ROW +{ + uint32_t Flags; + uint32_t Name; + uint32_t Namespace; + uint32_t Extends; + uint32_t Field; + uint32_t Method; +} TYPEDEF_ROW, *PTYPEDEF_ROW; + +// ECMA 335 - II.22.38 +typedef struct _TYPEREF_ROW +{ + uint32_t ResolutionScope; + uint32_t Name; + uint32_t Namespace; +} TYPEREF_ROW, *PTYPEREF_ROW; + +// ECMA 335 - II.22.39 +typedef struct _TYPESPEC_ROW +{ + uint32_t Signature; +} TYPESPEC_ROW, *PTYPESPEC_ROW; + +// ECMA 335 - II.22.23 +typedef struct _INTERFACEIMPL_ROW +{ + uint32_t Class; + uint32_t Interface; +} INTERFACEIMPL_ROW, *PINTERFACEIMPL_ROW; + +// ECMA 335 II.22.26 +typedef struct _METHODDEF_ROW +{ + uint32_t Rva; + uint16_t ImplFlags; + uint16_t Flags; + uint32_t Name; + uint32_t Signature; + uint32_t ParamList; +} METHODDEF_ROW, *PMETHODDEF_ROW; + +// ECMA 335 II.22.33 +typedef struct _PARAM_ROW +{ + uint16_t Flags; + uint16_t Sequence; + uint32_t Name; +} PARAM_ROW, *PPARAM_ROW; + +// ECMA 335 II.22.20 +typedef struct _GENERICPARAM_ROW +{ + uint16_t Number; + uint16_t Flags; + uint32_t Owner; + uint32_t Name; +} GENERICPARAM_ROW, *PGENERICPARAM_ROW; + +// ECMA 335 II.22.32 +typedef struct _NESTEDCLASS_ROW +{ + uint32_t NestedClass; + uint32_t EnclosingClass; +} NESTEDCLASS_ROW, *PNESTEDCLASS_ROW; + +// For easy passing of gen param collection +typedef struct _GENERIC_PARAMETERS +{ + char **names; + uint32_t len; +} GENERIC_PARAMETERS, *PGENERIC_PARAMETERS; + // Used to return offsets to the various headers. typedef struct _STREAMS { + int64_t metadata_root; // base from which are stream offsets relative PSTREAM_HEADER guid; PSTREAM_HEADER tilde; PSTREAM_HEADER string; @@ -321,9 +471,33 @@ typedef struct _STREAMS typedef struct _BLOB_PARSE_RESULT { uint8_t size; // Number of bytes parsed. This is the new offset. - DWORD length; // Value of the bytes parsed. This is the blob length. + uint32_t length; // Value of the bytes parsed. This is the blob length. } BLOB_PARSE_RESULT, *PBLOB_PARSE_RESULT; +typedef struct _TABLE_INFO +{ + uint8_t *Offset; + uint32_t RowCount; + uint32_t RowSize; +} TABLE_INFO, *PTABLE_INFO; + +// Structure that stores table information for parsing +typedef struct _TABLES +{ + TABLE_INFO typedef_; + TABLE_INFO typespec; + TABLE_INFO typeref; + TABLE_INFO methoddef; + TABLE_INFO param; + TABLE_INFO module; + TABLE_INFO moduleref; + TABLE_INFO assembly; + TABLE_INFO assemblyref; + TABLE_INFO intefaceimpl; + TABLE_INFO genericparam; + TABLE_INFO nestedclass; +} TABLES, *PTABLES; + // Used to store the number of rows of each table. typedef struct _ROWS { @@ -359,16 +533,40 @@ typedef struct _INDEX_SIZES uint8_t blob; uint8_t field; uint8_t methoddef; + uint8_t methodspec; uint8_t memberref; uint8_t param; uint8_t event; uint8_t typedef_; + uint8_t typeref; + uint8_t typespec; + uint8_t interfaceimpl; uint8_t property; uint8_t moduleref; + uint8_t module; uint8_t assemblyrefprocessor; uint8_t assemblyref; + uint8_t assembly; uint8_t genericparam; } INDEX_SIZES, *PINDEX_SIZES; +typedef struct _CLASS_CONTEXT +{ + PE* pe; + TABLES* tables; + INDEX_SIZES* index_sizes; + const uint8_t* str_heap; + uint32_t str_size; + const uint8_t* blob_heap; + uint32_t blob_size; +} CLASS_CONTEXT, *PCLASS_CONTEXT; + +typedef struct _PARAMETERS +{ + char* name; + char* type; + bool alloc; // if name is allocated and needs to be free +} PARAMETERS, *PPARAMETERS; + #pragma pack(pop) #endif diff --git a/libyara/modules/dotnet/dotnet.c b/libyara/modules/dotnet/dotnet.c index 8e1b7a2f..0d27fab5 100644 --- a/libyara/modules/dotnet/dotnet.c +++ b/libyara/modules/dotnet/dotnet.c @@ -18,49 +18,22 @@ limitations under the License. #include #include #include +#include #include #include #include #include #include #include +#include #include +#include #define MODULE_NAME dotnet -char* pe_get_dotnet_string( - PE* pe, - const uint8_t* string_offset, - DWORD string_index) -{ - size_t remaining; - - char* start; - char* eos; - - // Start of string must be within boundary - if (!(string_offset + string_index >= pe->data && - string_offset + string_index < pe->data + pe->data_size)) - return NULL; - - // Calculate how much until end of boundary, don't scan past that. - remaining = (pe->data + pe->data_size) - (string_offset + string_index); - - // Search for a NULL terminator from start of string, up to remaining. - start = (char*) (string_offset + string_index); - eos = (char*) memmem((void*) start, remaining, "\0", 1); - - // If no NULL terminator was found or the string is too large, return NULL. - if (eos == NULL || eos - start > 1024) - return NULL; - - return start; -} - -uint32_t max_rows(int count, ...) +static uint32_t max_rows(int count, ...) { va_list ap; - int i; uint32_t biggest; uint32_t x; @@ -70,7 +43,7 @@ uint32_t max_rows(int count, ...) va_start(ap, count); biggest = va_arg(ap, uint32_t); - for (i = 1; i < count; i++) + for (int i = 1; i < count; i++) { x = va_arg(ap, uint32_t); biggest = (x > biggest) ? x : biggest; @@ -80,55 +53,48 @@ uint32_t max_rows(int count, ...) return biggest; } -void dotnet_parse_guid( - PE* pe, - int64_t metadata_root, - PSTREAM_HEADER guid_header) +static uint32_t read_u32(const uint8_t** data) { - // GUIDs are 16 bytes each, converted to hex format plus separators and NULL. - char guid[37]; - int i = 0; + uint32_t result = yr_le32toh(yr_unaligned_u32(*data)); + *data += sizeof(uint32_t); + return result; +} - const uint8_t* guid_offset = pe->data + metadata_root + - yr_le32toh(guid_header->Offset); +static uint16_t read_u16(const uint8_t** data) +{ + uint16_t result = yr_le16toh(yr_unaligned_u16(*data)); + *data += sizeof(uint16_t); + return result; +} - DWORD guid_size = yr_le32toh(guid_header->Size); +static uint8_t read_u8(const uint8_t** data) +{ + uint8_t result = **data; + *data += sizeof(uint8_t); + return result; +} - // Limit the number of GUIDs to 16. - guid_size = yr_min(guid_size, 256); +static uint32_t read_index(const uint8_t** data, uint8_t len) +{ + if (len == 2) + return read_u16(data); + else + return read_u32(data); +} - // Parse GUIDs if we have them. GUIDs are 16 bytes each. - while (guid_size >= 16 && fits_in_pe(pe, guid_offset, 16)) - { - sprintf( - guid, - "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", - yr_le32toh(*(uint32_t*) guid_offset), - yr_le16toh(*(uint16_t*) (guid_offset + 4)), - yr_le16toh(*(uint16_t*) (guid_offset + 6)), - *(guid_offset + 8), - *(guid_offset + 9), - *(guid_offset + 10), - *(guid_offset + 11), - *(guid_offset + 12), - *(guid_offset + 13), - *(guid_offset + 14), - *(guid_offset + 15)); +// Returns valid offset within the table or NULL +const uint8_t* get_table_offset(const TABLE_INFO* tbl, uint32_t index) +{ + // Indexes to .NET tables are based from 1 + if (index < 1 || index > tbl->RowCount) + return NULL; - guid[(16 * 2) + 4] = '\0'; - - set_string(guid, pe->object, "guids[%i]", i); - - i++; - guid_size -= 16; - guid_offset += 16; - } - - set_integer(i, pe->object, "number_of_guids"); + return tbl->Offset + tbl->RowSize * (index - 1); } // Given an offset into a #US or #Blob stream, parse the entry at that position. // The offset is relative to the start of the PE file. +// if size > 0 then it's valid and readable blob BLOB_PARSE_RESULT dotnet_parse_blob_entry(PE* pe, const uint8_t* offset) { BLOB_PARSE_RESULT result; @@ -191,15 +157,1556 @@ BLOB_PARSE_RESULT dotnet_parse_blob_entry(PE* pe, const uint8_t* offset) return result; } - // There is an additional terminal byte which is 0x01 under certain - // conditions. The exact conditions are not relevant to our parsing but are - // documented in ECMA-335 II.24.2.4. - if (result.length > 0) - result.length--; + // Check if the length is actually readable + if (!fits_in_pe(pe, offset, result.size + result.length)) + { + result.size = 0; + return result; + } return result; } +char* pe_get_dotnet_string( + PE* pe, + const uint8_t* heap_offset, + uint32_t heap_size, + uint32_t string_index) +{ + size_t remaining; + + char* start; + char* eos; + + // Start of string must be within boundary + if (!(heap_offset + string_index >= pe->data && + heap_offset + string_index < pe->data + pe->data_size && + string_index < heap_size)) + return NULL; + + // Calculate how much until end of boundary, don't scan past that. + remaining = (pe->data + pe->data_size) - (heap_offset + string_index); + + // Search for a NULL terminator from start of string, up to remaining. + start = (char*) (heap_offset + string_index); + eos = (char*) memmem((void*) start, remaining, "\0", 1); + + // If no NULL terminator was found or the string is too large, return NULL. + if (eos == NULL || eos - start > 1024) + return NULL; + + return start; +} + +static bool is_nested(uint32_t flags) +{ + // ECMA 335 II.22.37 + // Whether a type is nested can be determined by the value of its + // Flags.Visibility sub-field – it shall be one of the set + // { NestedPublic, NestedPrivate, NestedFamily, NestedAssembly, + // NestedFamANDAssem, NestedFamORAssem } + + switch (flags & TYPE_ATTR_VISIBILITY_MASK) + { + case TYPE_ATTR_NESTED_PRIVATE: + case TYPE_ATTR_NESTED_PUBLIC: + case TYPE_ATTR_NESTED_FAMILY: + case TYPE_ATTR_NESTED_ASSEMBLY: + case TYPE_ATTR_NESTED_FAM_AND_ASSEM: + case TYPE_ATTR_NESTED_FAM_OR_ASSEM: + return true; + default: + return false; + } +} + +// ECMA 335 II.23.1.15 Flags for types [TypeAttribute] +static const char* get_type_visibility(uint32_t flags) +{ + switch (flags & TYPE_ATTR_VISIBILITY_MASK) + { + case TYPE_ATTR_NESTED_PRIVATE: + return "private"; + case TYPE_ATTR_PUBLIC: + case TYPE_ATTR_NESTED_PUBLIC: + return "public"; + case TYPE_ATTR_NESTED_FAMILY: + return "protected"; + case TYPE_ATTR_NOT_PUBLIC: + case TYPE_ATTR_NESTED_ASSEMBLY: + return "internal"; + case TYPE_ATTR_NESTED_FAM_AND_ASSEM: + return "private protected"; + case TYPE_ATTR_NESTED_FAM_OR_ASSEM: + return "protected internal"; + default: + return "private"; + } +} + +// ECMA 335 II.23.1.10 Flags for methods [MethodAttributes] +static const char* get_method_visibility(uint32_t flags) +{ + switch (flags & METHOD_ATTR_ACCESS_MASK) + { + case METHOD_ATTR_PRIVATE: + return "private"; + case METHOD_ATTR_FAM_AND_ASSEM: + return "private protected"; + case METHOD_ATTR_ASSEM: + return "internal"; + case METHOD_ATTR_FAMILY: + return "protected"; + case METHOD_ATTR_FAM_OR_ASSEM: + return "protected internal"; + case METHOD_ATTR_PUBLIC: + return "public"; + default: + return "private"; + } +} + +// ECMA 335 II.23.1.15 Flags for types [TypeAttribute] +static const char* get_typedef_type(uint32_t flags) +{ + switch (flags & TYPE_ATTR_CLASS_SEMANTIC_MASK) + { + case TYPE_ATTR_CLASS: + return "class"; + case TYPE_ATTR_INTERFACE: + return "interface"; + default: + return NULL; + } +} + +// returns allocated string ., must be freed +static char* create_full_name(const char* name, const char* namespace) +{ + if (!name || !strlen(name)) + return namespace ? strdup(namespace) : NULL; + + // No namespace -> return name only + if (!namespace || !strlen(namespace)) + { + // fix generic names + char* name_copy = yr_strdup(name); + char* end = strchr(name_copy, '`'); + if (end) + *end = 0; + return name_copy; + } + + size_t name_len = strlen(name); + size_t namespace_len = strlen(namespace); + + // . + char* full_name = yr_malloc(namespace_len + 1 + name_len + 1); + + memcpy(full_name, namespace, namespace_len); + full_name[namespace_len] = '.'; + memcpy(full_name + namespace_len + 1, name, name_len + 1); + + // fix generic names + char* end = strchr(full_name, '`'); + if (end) + *end = 0; + + return full_name; +} + +static bool read_typedef( + const CLASS_CONTEXT* ctx, + const uint8_t* data, + TYPEDEF_ROW* result) +{ + uint32_t row_size = ctx->tables->typedef_.RowSize; + + if (fits_in_pe(ctx->pe, data, row_size)) + { + uint8_t ext_size = 2; + uint32_t row_count = max_rows( + 3, + ctx->tables->typedef_.RowCount, + ctx->tables->typeref.RowCount, + ctx->tables->typespec.RowCount); + + if (row_count > (0xFFFF >> 0x02)) + ext_size = 4; + + result->Flags = read_u32(&data); + result->Name = read_index(&data, ctx->index_sizes->string); + result->Namespace = read_index(&data, ctx->index_sizes->string); + result->Extends = read_index(&data, ext_size); + result->Field = read_index(&data, ctx->index_sizes->field); + result->Method = read_index(&data, ctx->index_sizes->methoddef); + + return true; + } + + return false; +} + +static bool read_typeref( + const CLASS_CONTEXT* ctx, + const uint8_t* data, + TYPEREF_ROW* result) +{ + uint32_t row_size = ctx->tables->typeref.RowSize; + + if (fits_in_pe(ctx->pe, data, row_size)) + { + uint8_t res_size = 2; + uint32_t row_count = max_rows( + 4, + ctx->tables->module.RowCount, + ctx->tables->moduleref.RowCount, + ctx->tables->assemblyref.RowCount, + ctx->tables->typeref.RowCount); + + if (row_count > (0xFFFF >> 0x02)) + res_size = 4; + + result->ResolutionScope = read_index(&data, res_size); + result->Name = read_index(&data, ctx->index_sizes->string); + result->Namespace = read_index(&data, ctx->index_sizes->string); + + return true; + } + + return false; +} + +static bool read_interfaceimpl( + const CLASS_CONTEXT* ctx, + const uint8_t* data, + INTERFACEIMPL_ROW* result) +{ + uint32_t row_size = ctx->tables->intefaceimpl.RowSize; + + if (fits_in_pe(ctx->pe, data, row_size)) + { + uint32_t interface_size = 2; + uint32_t row_count = max_rows( + 3, + ctx->tables->typedef_.RowCount, + ctx->tables->typeref.RowCount, + ctx->tables->typespec.RowCount); + + if (row_count > (0xFFFF >> 0x02)) + interface_size = 4; + + result->Class = read_index(&data, ctx->index_sizes->typedef_); + result->Interface = read_index(&data, interface_size); + + return true; + } + + return false; +} + +static bool read_methoddef( + const CLASS_CONTEXT* ctx, + const uint8_t* data, + METHODDEF_ROW* result) +{ + uint32_t row_size = ctx->tables->methoddef.RowSize; + + if (fits_in_pe(ctx->pe, data, row_size)) + { + result->Rva = read_u32(&data); + result->ImplFlags = read_u16(&data); + result->Flags = read_u16(&data); + result->Name = read_index(&data, ctx->index_sizes->string); + result->Signature = read_index(&data, ctx->index_sizes->blob); + result->ParamList = read_index(&data, ctx->index_sizes->param); + return true; + } + + return false; +} + +static bool read_param( + const CLASS_CONTEXT* ctx, + const uint8_t* data, + PARAM_ROW* result) +{ + uint32_t row_size = ctx->tables->param.RowSize; + + if (fits_in_pe(ctx->pe, data, row_size)) + { + result->Flags = read_u16(&data); + result->Sequence = read_u16(&data); + result->Name = read_index(&data, ctx->index_sizes->string); + return true; + } + + return false; +} + +static bool read_genericparam( + const CLASS_CONTEXT* ctx, + const uint8_t* data, + GENERICPARAM_ROW* result) +{ + uint32_t row_size = ctx->tables->genericparam.RowSize; + + if (fits_in_pe(ctx->pe, data, row_size)) + { + uint32_t owner_idx_size = 2; + uint32_t row_count = max_rows( + 2, ctx->tables->typedef_.RowCount, ctx->tables->methoddef.RowCount); + + if (row_count > (0xFFFF >> 0x01)) + owner_idx_size = 4; + + result->Number = read_u16(&data); + result->Flags = read_u16(&data); + result->Owner = read_index(&data, owner_idx_size); + result->Name = read_index(&data, ctx->index_sizes->string); + return true; + } + + return false; +} + +static bool read_typespec( + const CLASS_CONTEXT* ctx, + const uint8_t* data, + TYPESPEC_ROW* result) +{ + uint32_t row_size = ctx->tables->typespec.RowSize; + + if (fits_in_pe(ctx->pe, data, row_size)) + { + result->Signature = read_index(&data, ctx->index_sizes->blob); + return true; + } + + return false; +} + +static bool read_nestedclass( + const CLASS_CONTEXT* ctx, + const uint8_t* data, + NESTEDCLASS_ROW* result) +{ + uint32_t row_size = ctx->tables->nestedclass.RowSize; + + if (fits_in_pe(ctx->pe, data, row_size)) + { + result->NestedClass = read_index(&data, ctx->index_sizes->typedef_); + result->EnclosingClass = read_index(&data, ctx->index_sizes->typedef_); + return true; + } + + return false; +} + +// ECMA-335 II.23.2 blob heap uses variable length encoding of integers +static uint32_t read_blob_unsigned(const uint8_t** data, uint32_t* len) +{ + if (*len < 1) + return 0; + + // first byte is enough to decode the length + // without worrying about endiannity + // Compressed integers use big-endian order + uint8_t first_byte = *(*data); + + // If the value lies between 0 (0x00) and 127 (0x7F), inclusive, encode as a + // one-byte integer (bit 7 is clear, value held in bits 6 through 0) + if (!(first_byte & 0x80)) + { + *data += sizeof(uint8_t); + *len -= sizeof(uint8_t); + return first_byte; + } + + if (*len < 2) + return 0; + + // If the value lies between 2^8 (0x80) and 2^14 – 1 (0x3FFF), inclusive, + // encode as a 2-byte integer with bit 15 set, bit 14 clear (value held in + // bits 13 through 0) + if ((first_byte & 0xC0) == 0x80) + { + uint32_t result = yr_be16toh(*(uint16_t*) *data); + *data += sizeof(uint16_t); + *len -= sizeof(uint16_t); + // value is in lower 14 bits + return result & 0x3FFF; + } + + if (*len < 4) + return 0; + + // Otherwise, encode as a 4-byte integer, with bit 31 set, bit 30 set, + // bit 29 clear (value held in bits 28 through 0) + if ((first_byte & 0xE0) == 0xC0) + { + uint32_t result = yr_be32toh(*(uint32_t*) *data); + *data += sizeof(uint32_t); + *len -= sizeof(uint32_t); + // Uses last 29 bits for the result + return result & 0x1FFFFFFF; + } + + return 0; +} + +// ECMA-335 II.23.2 blob heap uses variable length encoding of integers +// Probably wouldn't work on non 2's complement arches? +static int32_t read_blob_signed(const uint8_t** data, uint32_t* len) +{ + // Compressed integers use big-endian order! + if (*len < 1) + return 0; + + // first byte is enough to decode the length + // without worrying about endiannity + uint8_t first_byte = *(*data); + + // Encode as a one-byte integer, bit 7 clear, rotated value in bits 6 + // through 0, giving 0x01 (-2^6) to 0x7E (2^6-1). + if (!(first_byte & 0x80)) + { + uint8_t tmp = first_byte >> 1; + // sign extension in case of negative number + if (first_byte & 0x1) + tmp |= 0xC0; + + *data += sizeof(uint8_t); + *len -= sizeof(uint8_t); + + return (int32_t) tmp; + } + + if (*len < 2) + return 0; + + // Encode as a two-byte integer: bit 15 set, bit 14 clear, rotated value + // in bits 13 through 0, giving 0x8001 (-2^13) to 0xBFFE (2^13-1). + if ((first_byte & 0xC0) == 0x80) + { + uint16_t tmp1 = yr_be16toh(*(uint16_t*) *data); + // shift and leave top 2 bits clear + uint16_t tmp2 = (tmp1 >> 1) & 0x3FFF; + // sign extension in case of negative number + if (tmp1 & 0x1) + tmp2 |= 0xC000; + + *data += sizeof(uint16_t); + *len -= sizeof(uint16_t); + + return (int32_t) tmp2; + } + + // Encode as a four-byte integer: bit 31 set, 30 set, bit 29 clear, + // rotated value in bits 28 through 0, giving 0xC0000001 (-2^28) to + // 0xDFFFFFFE (2^28-1). + if ((first_byte & 0xE0) == 0xC0) + { + uint32_t tmp1 = yr_be32toh(*(uint32_t*) *data); + // shift and leave top 3 bits clear + uint32_t tmp2 = (tmp1 >> 1) & 0x1FFFFFFF; + // sign extension in case of negative number + if (tmp1 & 0x1) + tmp2 |= 0xE0000000; + + *data += sizeof(uint32_t); + *len -= sizeof(uint32_t); + + return (int32_t) tmp2; + } + + return 0; +} + +// Forward declarations +static char* parse_signature_type( + const CLASS_CONTEXT* ctx, + const uint8_t** data, + uint32_t* len, + GENERIC_PARAMETERS* class_gen_params, + GENERIC_PARAMETERS* method_gen_params, + uint32_t depth); + +static char* parse_enclosing_types(const CLASS_CONTEXT* ctx, uint32_t type_idx); + +static char* get_type_def_or_ref_fullname( + const CLASS_CONTEXT* ctx, + uint32_t coded_index, + GENERIC_PARAMETERS* class_gen_params, + GENERIC_PARAMETERS* method_gen_params) +{ + // first 2 bits define table, index starts with third bit + uint32_t index = coded_index >> 2; + if (!index) + return NULL; + + const uint8_t* str_heap = ctx->str_heap; + uint32_t str_size = ctx->str_size; + + uint8_t table = coded_index & 0x3; + if (table == 0) // TypeDef + { + const uint8_t* data = get_table_offset(&ctx->tables->typedef_, index); + if (!data) + return NULL; + + TYPEDEF_ROW def_row; + bool result = read_typedef(ctx, data, &def_row); + if (result) + { + const char* name = pe_get_dotnet_string( + ctx->pe, str_heap, str_size, def_row.Name); + const char* namespace = pe_get_dotnet_string( + ctx->pe, str_heap, str_size, def_row.Namespace); + + char* result = NULL; + // Type might be nested, try to find correct namespace + if (is_nested(def_row.Flags)) + { + char* nested_namespace = parse_enclosing_types(ctx, index); + char* tmp = create_full_name(namespace, nested_namespace); + result = create_full_name(name, tmp); + yr_free(nested_namespace); + yr_free(tmp); + } + else + result = create_full_name(name, namespace); + + return result; + } + } + else if (table == 1) // TypeRef + { + const uint8_t* data = get_table_offset(&ctx->tables->typeref, index); + if (!data) + return NULL; + + TYPEREF_ROW ref_row; + bool result = read_typeref(ctx, data, &ref_row); + if (result) + { + const char* name = pe_get_dotnet_string( + ctx->pe, str_heap, str_size, ref_row.Name); + const char* namespace = pe_get_dotnet_string( + ctx->pe, str_heap, str_size, ref_row.Namespace); + + return create_full_name(name, namespace); + } + } + else if (table == 2) // TypeSpec + { + const uint8_t* data = get_table_offset(&ctx->tables->typespec, index); + if (!data) + return NULL; + + TYPESPEC_ROW spec_row; + bool result = read_typespec(ctx, data, &spec_row); + if (result) + { + const uint8_t* sig_data = ctx->blob_heap + spec_row.Signature; + + // Read the blob entry with the data + BLOB_PARSE_RESULT blob_res = dotnet_parse_blob_entry(ctx->pe, sig_data); + sig_data += blob_res.size; + uint32_t sig_len = blob_res.length; + + // Valid blob + if (blob_res.size) + return parse_signature_type( + ctx, &sig_data, &sig_len, class_gen_params, NULL, 0); + } + } + return NULL; +} + +static char* parse_signature_type( + const CLASS_CONTEXT* ctx, + const uint8_t** data, + uint32_t* len, + GENERIC_PARAMETERS* class_gen_params, + GENERIC_PARAMETERS* method_gen_params, + uint32_t depth // against loops +) +{ + // If atleast first type fits and we are not too nested + if (*len < 1 || !fits_in_pe(ctx->pe, *data, 1) || depth > MAX_TYPE_DEPTH) + return NULL; + + bool class = false; + uint32_t coded_index, index; + char* tmp = NULL; + char* ret_type = NULL; + + uint8_t type = read_u8(data); + *len -= 1; + + switch (type) + { + case TYPE_VOID: + ret_type = "void"; + break; + + case TYPE_BOOL: + ret_type = "bool"; + break; + + case TYPE_CHAR: + ret_type = "char"; + break; + + case TYPE_I1: + ret_type = "sbyte"; + break; + + case TYPE_U1: + ret_type = "byte"; + break; + + case TYPE_I2: + ret_type = "short"; + break; + + case TYPE_U2: + ret_type = "ushort"; + break; + + case TYPE_I4: + ret_type = "int"; + break; + + case TYPE_U4: + ret_type = "uint"; + break; + + case TYPE_I8: + ret_type = "long"; + break; + + case TYPE_U8: + ret_type = "ulong"; + break; + + case TYPE_R4: + ret_type = "float"; + break; + + case TYPE_R8: + ret_type = "double"; + break; + + case TYPE_STRING: + ret_type = "string"; + break; + + case TYPE_TYPEDREF: + ret_type = "TypedReference"; + break; + + case TYPE_I: + ret_type = "IntPtr"; + break; + + case TYPE_U: + ret_type = "UIntPtr"; + break; + + case TYPE_PTR: // Ptr followed by type + tmp = parse_signature_type( + ctx, data, len, class_gen_params, method_gen_params, depth + 1); + if (tmp) + { + SIMPLE_STR* ss = sstr_new(NULL); + if (!ss) + { + yr_free(tmp); + break; + } + bool res = sstr_appendf(ss, "Ptr<%s>", tmp); + if (res) + ret_type = sstr_move(ss); + + yr_free(tmp); + sstr_free(ss); + return ret_type; + } + break; + + case TYPE_BYREF: + // ByRef followed by type + tmp = parse_signature_type( + ctx, data, len, class_gen_params, method_gen_params, depth + 1); + if (tmp) + { + SIMPLE_STR* ss = sstr_new(NULL); + if (!ss) + { + yr_free(tmp); + break; + } + bool res = sstr_appendf(ss, "ref %s", tmp); + if (res) + ret_type = sstr_move(ss); + + yr_free(tmp); + sstr_free(ss); + return ret_type; + } + break; + + case TYPE_VALUETYPE: // ValueType + case TYPE_CLASS: // Class + // followed by TypeDefOrRefOrSpecEncoded index + coded_index = read_blob_unsigned(data, len); + return get_type_def_or_ref_fullname( + ctx, coded_index, class_gen_params, method_gen_params); + break; + + case TYPE_VAR: // Generic class Var + class = true; + case TYPE_MVAR: // Generic method Var + index = read_blob_unsigned(data, len); + // return class generic var or method generic var + if (class && class_gen_params && index < class_gen_params->len) + ret_type = class_gen_params->names[index]; + else if (!class && method_gen_params && index < method_gen_params->len) + ret_type = method_gen_params->names[index]; + break; + + case TYPE_ARRAY: + { + // Array -> Type -> Rank -> NumSizes -> Size -> NumLobound -> LoBound + char* tmp = parse_signature_type( + ctx, data, len, class_gen_params, method_gen_params, depth + 1); + if (!tmp) + break; + + uint32_t* sizes = NULL; + int32_t* lo_bounds = NULL; + + // Read number of dimensions + uint32_t rank = read_blob_unsigned(data, len); + if (!rank || rank > MAX_ARRAY_RANK) + goto cleanup; + + // Read number of specified sizes + uint32_t num_sizes = read_blob_unsigned(data, len); + sizes = yr_malloc(sizeof(uint32_t) * num_sizes); + if (!sizes || num_sizes > rank) + goto cleanup; + + for (uint32_t i = 0; i < num_sizes; ++i) + { + sizes[i] = read_blob_unsigned(data, len); + } + + // Read number of specified lower bounds + uint32_t num_lowbounds = read_blob_unsigned(data, len); + lo_bounds = yr_malloc(sizeof(int32_t) * num_lowbounds); + if (!lo_bounds || num_lowbounds > rank) + goto cleanup; + + for (uint32_t i = 0; i < num_lowbounds; ++i) + { + lo_bounds[i] = read_blob_signed(data, len); + + // Adjust higher bound according to lower bound + if (num_sizes > i) + sizes[i] += lo_bounds[i]; + } + + // Build the resulting array type + SIMPLE_STR* ss = sstr_new(NULL); + if (!ss) + goto cleanup; + + sstr_appendf(ss, "%s[", tmp); + + for (uint32_t i = 0; i < rank; ++i) + { + if (num_sizes > i || num_lowbounds > i) + { + if (num_lowbounds > i && lo_bounds[i] != 0) + sstr_appendf(ss, "%lu...", lo_bounds[i]); + if (num_sizes > i && sizes[i] != 0) + sstr_appendf(ss, "%lu", sizes[i]); + } + if (i + 1 != rank) + sstr_appendf(ss, ","); + } + bool res = sstr_appendf(ss, "]"); + if (res) + ret_type = sstr_move(ss); + + yr_free(sizes); + yr_free(lo_bounds); + yr_free(tmp); + sstr_free(ss); + return ret_type; + + cleanup: + yr_free(sizes); + yr_free(lo_bounds); + yr_free(tmp); + } + break; + + case TYPE_GENERICINST: + { + tmp = parse_signature_type( + ctx, data, len, class_gen_params, method_gen_params, depth + 1); + + if (!tmp) + break; + + uint32_t gen_count = read_blob_unsigned(data, len); + // Sanity check for corrupted files + if (gen_count > MAX_GEN_PARAM_COUNT) + { + yr_free(tmp); + break; + } + + SIMPLE_STR* ss = sstr_new(NULL); + if (!ss) + { + yr_free(tmp); + break; + } + sstr_appendf(ss, "%s<", tmp); + yr_free(tmp); + + for (int i = 0; i < gen_count; i++) + { + char* param_type = parse_signature_type( + ctx, data, len, class_gen_params, method_gen_params, depth + 1); + if (i) + sstr_appendf(ss, ","); + if (param_type) + sstr_appendf(ss, "%s", param_type); + + yr_free(param_type); + } + bool res = sstr_appendf(ss, ">"); + if (res) + ret_type = sstr_move(ss); + + sstr_free(ss); + return ret_type; + } + break; + + case TYPE_FNPTR: + if (*len > 0) + { // Flags -> ParamCount -> RetType -> Param -> Sentinel ->Param + // Skip flags + (*data)++; + (*len)--; + + uint32_t param_count = read_blob_unsigned(data, len); + tmp = parse_signature_type( + ctx, data, len, class_gen_params, method_gen_params, depth + 1); + + if (!tmp) + break; + + SIMPLE_STR* ss = sstr_new(NULL); + if (!ss) + { + yr_free(tmp); + break; + } + + sstr_appendf(ss, "FnPtr<%s(", tmp); + yr_free(tmp); + + for (int i = 0; i < param_count; i++) + { + char* param_type = parse_signature_type( + ctx, data, len, class_gen_params, method_gen_params, depth + 1); + if (i) + sstr_appendf(ss, ", "); + if (param_type) + sstr_appendf(ss, "%s", param_type); + + yr_free(param_type); + } + bool res = sstr_appendf(ss, ")>"); + if (res) + ret_type = sstr_move(ss); + + sstr_free(ss); + return ret_type; + } + break; + + case TYPE_OBJECT: + ret_type = "object"; + break; + + case TYPE_SZARRAY: + // Single dimensional array followed by type + tmp = parse_signature_type( + ctx, data, len, class_gen_params, method_gen_params, depth + 1); + if (tmp) + { + SIMPLE_STR* ss = sstr_newf("%s[]", tmp); + if (ss) + ret_type = sstr_move(ss); + + yr_free(tmp); + sstr_free(ss); + return ret_type; + } + break; + + case TYPE_CMOD_REQD: // Req modifier + case TYPE_CMOD_OPT: // Opt modifier + { + // What is point of these + // Right now ignore them... + read_blob_unsigned(data, len); + return parse_signature_type( + ctx, data, len, class_gen_params, method_gen_params, depth + 1); + } + break; + + default: + break; + } + + if (ret_type) + return yr_strdup(ret_type); + else + return NULL; +} + +static void parse_type_parents( + const CLASS_CONTEXT* ctx, + uint32_t extends, + uint32_t type_idx, + uint32_t out_idx, // Class idx in output array + GENERIC_PARAMETERS* class_gen_params) +{ + // Find the parent class + char* parent = get_type_def_or_ref_fullname( + ctx, extends, class_gen_params, NULL); + + uint32_t base_type_idx = 0; + if (parent) + { + set_string( + parent, + ctx->pe->object, + "classes[%i].base_types[%i]", + out_idx, + base_type_idx++); + + yr_free(parent); + } + + // linear search for every interface that the class implements + for (uint32_t idx = 0; idx < ctx->tables->intefaceimpl.RowCount; ++idx) + { + const uint8_t* data = get_table_offset(&ctx->tables->intefaceimpl, idx + 1); + if (!data) + break; + + INTERFACEIMPL_ROW row = {0}; + bool result = read_interfaceimpl(ctx, data, &row); + if (!result) + continue; + + // We found the inherited interface + if (row.Class == type_idx) + { + char* inteface = get_type_def_or_ref_fullname( + ctx, row.Interface, class_gen_params, NULL); + if (inteface) + { + set_string( + inteface, + ctx->pe->object, + "classes[%i].base_types[%i]", + out_idx, + base_type_idx++); + + yr_free(inteface); + } + } + } + set_integer( + base_type_idx, + ctx->pe->object, + "classes[%i].number_of_base_types", + out_idx); +} + +// Returns true if all parameters were correctly parsed +static bool parse_method_params( + const CLASS_CONTEXT* ctx, + uint32_t param_list, + uint32_t method_idx, // used for output + uint32_t class_idx, + uint32_t param_count, + const uint8_t* sig_data, + uint32_t sig_len, + GENERIC_PARAMETERS* class_gen_params, + GENERIC_PARAMETERS* method_gen_params) +{ + if (!param_list) // NULL + return true; + + const uint8_t* str_heap = ctx->str_heap; + uint32_t str_size = ctx->str_size; + + // Array to hold all the possible parameters + PARAMETERS* params = yr_calloc(param_count, sizeof(PARAMETERS)); + if (!params) + return false; + + for (uint32_t idx = 0; idx < param_count; ++idx) + { + const uint8_t* data = get_table_offset( + &ctx->tables->param, param_list + idx); + + char* name = NULL; + bool alloc = false; // Flag if name needs freeing + + if (data) // We need param table mostly just for the param name + { + PARAM_ROW row = {0}; + bool result = read_param(ctx, data, &row); + if (!result) + { // Cleanup and return + for (uint32_t j = 0; j < idx; ++j) + { + if (params[j].alloc) + yr_free(params[j].name); + + yr_free(params[j].type); + } + yr_free(params); + return false; + } + + name = pe_get_dotnet_string(ctx->pe, str_heap, str_size, row.Name); + } + else // We can reconstruct their type from the signature + // and give them default name + { + alloc = true; + SIMPLE_STR* ss = sstr_newf("P_%lu", idx); + if (ss) + { + name = sstr_move(ss); + sstr_free(ss); + } + } + + char* type = parse_signature_type( + ctx, &sig_data, &sig_len, class_gen_params, method_gen_params, 0); + + params[idx].alloc = alloc; + params[idx].name = name; + params[idx].type = type; + + if (!type) // If any param fails, whole parsing is aborted + { + for (uint32_t j = 0; j <= idx; ++j) + { + if (params[j].alloc) + yr_free(params[j].name); + + yr_free(params[j].type); + } + yr_free(params); + return false; + } + } + + // If we got all of them correctly, write to output and cleanup + YR_OBJECT* out_obj = ctx->pe->object; + set_integer( + param_count, + out_obj, + "classes[%i].methods[%i].number_of_parameters", + class_idx, + method_idx); + + for (uint32_t i = 0; i < param_count; ++i) + { + set_string( + params[i].name, + out_obj, + "classes[%i].methods[%i].parameters[%i].name", + class_idx, + method_idx, + i); + set_string( + params[i].type, + out_obj, + "classes[%i].methods[%i].parameters[%i].type", + class_idx, + method_idx, + i); + if (params[i].alloc) + yr_free(params[i].name); + + yr_free(params[i].type); + } + + yr_free(params); + return true; +} + +// Walks GenericParam table, finds all generic params for the MethodDef or +// TypeDef entry and allocates buffer with the Generic param names into result +static void parse_generic_params( + const CLASS_CONTEXT* ctx, + bool method, // true means MethodDef, false TypeDef index + uint32_t gen_idx, + GENERIC_PARAMETERS* result) +{ + const uint8_t* str_heap = ctx->str_heap; + uint32_t str_size = ctx->str_size; + + result->names = NULL; + result->len = 0; + + // Walk the GenericParam table to find GenParameters of the class/method + for (uint32_t idx = 0; idx < ctx->tables->genericparam.RowCount; ++idx) + { + const uint8_t* data = get_table_offset(&ctx->tables->genericparam, idx + 1); + if (!data) + goto cleanup; + + GENERICPARAM_ROW row = {0}; + bool read_result = read_genericparam(ctx, data, &row); + if (!read_result) + continue; + + // TypeOrMethodDef coded index + uint8_t table = row.Owner & 0x1; + // 0 == TypeDef 1 == MethodDef + // Check if it's generic param of the type we want + if (table == method && (row.Owner >> 1) == gen_idx) + { + char* name = pe_get_dotnet_string(ctx->pe, str_heap, str_size, row.Name); + // name must be valid string + if (!name || !*name) // ERROR + goto cleanup; + + result->len += 1; + char** tmp = yr_realloc(result->names, result->len * sizeof(char*)); + if (!tmp) + goto cleanup; + + // Update the collection + result->names = tmp; + result->names[result->len - 1] = name; + } + } + return; + +cleanup: + yr_free(result->names); + result->names = NULL; + result->len = 0; +} + +static void parse_methods( + const CLASS_CONTEXT* ctx, + uint32_t methodlist, + uint32_t method_count, + uint32_t class_idx, // class index in the YARA output + GENERIC_PARAMETERS* class_gen_params) +{ + if (!methodlist) + return; + + const uint8_t* str_heap = ctx->str_heap; + uint32_t str_size = ctx->str_size; + + uint32_t out_idx = 0; + for (uint32_t idx = 0; idx < method_count; ++idx) + { + const uint8_t* data = get_table_offset( + &ctx->tables->methoddef, methodlist + idx); + if (!data) + break; + + METHODDEF_ROW row = {0}; + bool result = read_methoddef(ctx, data, &row); + if (!result) + continue; + + const char* name = pe_get_dotnet_string( + ctx->pe, str_heap, str_size, row.Name); + + // Ignore invalid/empty names + if (!name || !*name) + continue; + + // Try to find generic params for the method + GENERIC_PARAMETERS method_gen_params = {0}; + parse_generic_params(ctx, true, methodlist + idx, &method_gen_params); + + // Read the blob entry with signature data + const uint8_t* sig_data = ctx->blob_heap + row.Signature; + BLOB_PARSE_RESULT blob_res = dotnet_parse_blob_entry(ctx->pe, sig_data); + sig_data += blob_res.size; + uint32_t sig_len = blob_res.length; + + uint32_t param_count = 0; + char* return_type = NULL; + // If there is valid blob and atleast minimum to parse + // (flags, paramCount, retType) parse these basic information + if (blob_res.size && sig_len >= 3) + { + uint8_t flags = read_u8(&sig_data); + sig_len -= 1; + if (flags & SIG_FLAG_GENERIC) + // Generic param count, ignored as we get the + // information from generic param table + (void) read_blob_unsigned(&sig_data, &sig_len); + + // Regular param count + param_count = read_blob_unsigned(&sig_data, &sig_len); + return_type = parse_signature_type( + ctx, &sig_data, &sig_len, class_gen_params, &method_gen_params, 0); + } + else // Error, skip + goto clean_next; + + // Sanity check for corrupted files + if (!return_type || param_count > MAX_PARAM_COUNT) + goto clean_next; + + result = parse_method_params( + ctx, + row.ParamList, + out_idx, + class_idx, + param_count, + sig_data, + sig_len, + class_gen_params, + &method_gen_params); + if (!result) + goto clean_next; + + const char* visibility = get_method_visibility(row.Flags); + uint32_t stat = (row.Flags & METHOD_ATTR_STATIC) != 0; + uint32_t final = (row.Flags & METHOD_ATTR_FINAL) != 0; + uint32_t virtual = (row.Flags & METHOD_ATTR_VIRTUAL) != 0; + uint32_t abstract = (row.Flags & METHOD_ATTR_ABSTRACT) != 0; + + YR_OBJECT* out_obj = ctx->pe->object; + set_string( + name, out_obj, "classes[%i].methods[%i].name", class_idx, out_idx); + set_string( + visibility, + out_obj, + "classes[%i].methods[%i].visibility", + class_idx, + out_idx); + set_integer( + stat, out_obj, "classes[%i].methods[%i].static", class_idx, out_idx); + set_integer( + virtual, + out_obj, + "classes[%i].methods[%i].virtual", + class_idx, + out_idx); + set_integer( + final, out_obj, "classes[%i].methods[%i].final", class_idx, out_idx); + set_integer( + abstract, + out_obj, + "classes[%i].methods[%i].abstract", + class_idx, + out_idx); + set_integer( + method_gen_params.len, + out_obj, + "classes[%i].methods[%i].number_of_generic_parameters", + class_idx, + out_idx); + + for (uint32_t i = 0; i < method_gen_params.len; ++i) + { + set_string( + method_gen_params.names[i], + ctx->pe->object, + "classes[%i].methods[%i].generic_parameters[%i]", + class_idx, + out_idx, + i); + } + + // Unset return type for constructors for FileInfo compatibility + if (strcmp(name, ".ctor") != 0 && strcmp(name, ".cctor") != 0) + { + set_string( + return_type, + out_obj, + "classes[%i].methods[%i].return_type", + class_idx, + out_idx); + } + + out_idx++; + clean_next: + yr_free(return_type); + yr_free(method_gen_params.names); + } + + set_integer( + out_idx, ctx->pe->object, "classes[%i].number_of_methods", class_idx); +} + +// Walks NestedClass table, returns enclosing type fullname or NULL +static char* parse_enclosing_types( + const CLASS_CONTEXT* ctx, + uint32_t nested_idx) +{ + const uint8_t* str_heap = ctx->str_heap; + uint32_t str_size = ctx->str_size; + + for (uint32_t idx = 0; idx < ctx->tables->nestedclass.RowCount; ++idx) + { + const uint8_t* nested_data = get_table_offset( + &ctx->tables->nestedclass, idx + 1); + + NESTEDCLASS_ROW nested_row = {0}; + bool read_result = read_nestedclass(ctx, nested_data, &nested_row); + if (!read_result) + continue; + + // We found enclosing class, get the namespace + if (nested_row.NestedClass == nested_idx) + { + const uint8_t* typedef_data = get_table_offset( + &ctx->tables->typedef_, nested_row.EnclosingClass); + + TYPEDEF_ROW typedef_row = {0}; + bool result = read_typedef(ctx, typedef_data, &typedef_row); + if (!result) + break; + + const char* name = pe_get_dotnet_string( + ctx->pe, str_heap, str_size, typedef_row.Name); + + // Skip the Module pseudo class + if (name && strcmp(name, "") == 0) + break; + + const char* namespace = pe_get_dotnet_string( + ctx->pe, str_heap, str_size, typedef_row.Namespace); + + // Type might be further nested, try to find correct namespace, + // check for self-reference + if (is_nested(typedef_row.Flags) && + nested_row.EnclosingClass != nested_row.NestedClass) + { + char* nested_namespace = parse_enclosing_types( + ctx, nested_row.EnclosingClass); + + char* tmp = create_full_name(namespace, nested_namespace); + char* fullname = create_full_name(name, tmp); + yr_free(nested_namespace); + yr_free(tmp); + return fullname; + } + + return create_full_name(name, namespace); + } + } + + return NULL; +} + +// Parses and reconstructs user defined types with their methods and base types +static void parse_user_types(const CLASS_CONTEXT* ctx) +{ + const uint8_t* str_heap = ctx->str_heap; + uint32_t str_size = ctx->str_size; + + // Index for output tracking, we can't use + // offset as some classes can get skipped + uint32_t out_idx = 0; + // skip first class as it's module pseudo class -> start at index 1 + for (uint32_t idx = 0; idx < ctx->tables->typedef_.RowCount; ++idx) + { + YR_OBJECT* out_obj = ctx->pe->object; + // Tables indexing starts at 1 + const uint8_t* data = get_table_offset(&ctx->tables->typedef_, idx + 1); + + TYPEDEF_ROW row = {0}; + bool result = read_typedef(ctx, data, &row); + if (!result) + continue; + + const char* name = pe_get_dotnet_string( + ctx->pe, str_heap, str_size, row.Name); + const char* type = get_typedef_type(row.Flags); + + // Ignore invalid types and invalid (empty) names + if (!name || !*name || !type) + continue; + + // If the type is generic, it will include ` at the end of a name + // with number of generic arguments, just use the part before that + const char* end = strchr(name, '`'); + // If the name will turn out empty, skip it and skip Module pseudo class + if (end == name || strcmp(name, "") == 0) + continue; + + if (end) + set_sized_string(name, end - name, out_obj, "classes[%i].name", out_idx); + else + set_string(name, out_obj, "classes[%i].name", out_idx); + + char* fullname = NULL; + char* namespace = pe_get_dotnet_string( + ctx->pe, str_heap, str_size, row.Namespace); + + // Type might be nested, if so -> find correct namespace + if (is_nested(row.Flags)) + { + char* nested_namespace = parse_enclosing_types(ctx, idx + 1); + namespace = create_full_name(namespace, nested_namespace); + set_string(namespace, out_obj, "classes[%i].namespace", out_idx); + fullname = create_full_name(name, namespace); + yr_free(nested_namespace); + yr_free(namespace); + } + else + { + set_string(namespace, out_obj, "classes[%i].namespace", out_idx); + fullname = create_full_name(name, namespace); + } + + const char* visibility = get_type_visibility(row.Flags); + uint32_t abstract = (row.Flags & TYPE_ATTR_ABSTRACT) != 0; + uint32_t sealed = (row.Flags & TYPE_ATTR_SEALED) != 0; + + set_string(fullname, out_obj, "classes[%i].fullname", out_idx); + set_string(visibility, out_obj, "classes[%i].visibility", out_idx); + set_string(type, out_obj, "classes[%i].type", out_idx); + set_integer(abstract, out_obj, "classes[%i].abstract", out_idx); + set_integer(sealed, out_obj, "classes[%i].sealed", out_idx); + + yr_free(fullname); + + // Find if type has any Generic parameters + GENERIC_PARAMETERS gen_params = {0}; + parse_generic_params(ctx, false, idx + 1, &gen_params); + + set_integer( + gen_params.len, + out_obj, + "classes[%i].number_of_generic_parameters", + out_idx); + + for (uint32_t i = 0; i < gen_params.len; ++i) + { + set_string( + gen_params.names[i], + out_obj, + "classes[%i].generic_parameters[%i]", + out_idx, + i); + } + // Find type and interfaces the type inherits + parse_type_parents(ctx, row.Extends, idx + 1, out_idx, &gen_params); + + // To get the number of methods, we must peek where the MethodList + // of the next type is, then there is next.MethodList - this.MethodList + // number of methods, or if there is no following type, + // the rest of the MethodDef table is used + uint32_t method_count = 0; + // If there is next method + if (idx + 1 < ctx->tables->typedef_.RowCount) + { + const uint8_t* data = get_table_offset(&ctx->tables->typedef_, idx + 2); + + TYPEDEF_ROW next_row = {0}; + result = read_typedef(ctx, data, &next_row); + + // overflow check + if (result && next_row.Method >= row.Method) + method_count = next_row.Method - row.Method; + } + // overflow check - use the rest of the methods in the table + else if (ctx->tables->methoddef.RowCount >= row.Method) + { + method_count = ctx->tables->methoddef.RowCount + 1 - row.Method; + } + + // Sanity check for corrupted files + if (method_count <= MAX_METHOD_COUNT) + parse_methods(ctx, row.Method, method_count, out_idx, &gen_params); + + yr_free(gen_params.names); + out_idx++; + } + + set_integer(out_idx, ctx->pe->object, "number_of_classes"); +} + +void dotnet_parse_guid( + PE* pe, + int64_t metadata_root, + PSTREAM_HEADER guid_header) +{ + // GUIDs are 16 bytes each, converted to hex format plus separators and NULL. + char guid[37]; + int i = 0; + + const uint8_t* guid_offset = pe->data + metadata_root + + yr_le32toh(guid_header->Offset); + + DWORD guid_size = yr_le32toh(guid_header->Size); + + // Limit the number of GUIDs to 16. + guid_size = yr_min(guid_size, 256); + + // Parse GUIDs if we have them. GUIDs are 16 bytes each. + while (guid_size >= 16 && fits_in_pe(pe, guid_offset, 16)) + { + sprintf( + guid, + "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + yr_le32toh(*(uint32_t*) guid_offset), + yr_le16toh(*(uint16_t*) (guid_offset + 4)), + yr_le16toh(*(uint16_t*) (guid_offset + 6)), + *(guid_offset + 8), + *(guid_offset + 9), + *(guid_offset + 10), + *(guid_offset + 11), + *(guid_offset + 12), + *(guid_offset + 13), + *(guid_offset + 14), + *(guid_offset + 15)); + + guid[(16 * 2) + 4] = '\0'; + + set_string(guid, pe->object, "guids[%i]", i); + + i++; + guid_size -= 16; + guid_offset += 16; + } + + set_integer(i, pe->object, "number_of_guids"); +} + void dotnet_parse_us(PE* pe, int64_t metadata_root, PSTREAM_HEADER us_header) { BLOB_PARSE_RESULT blob_result; @@ -230,9 +1737,15 @@ void dotnet_parse_us(PE* pe, int64_t metadata_root, PSTREAM_HEADER us_header) break; offset += blob_result.size; + // There is an additional terminal byte which is 0x01 under certain + // conditions - when any top bit in utf16 top byte is set. + // The exact conditions are not relevant to our parsing but are + // documented in ECMA-335 II.24.2.4. + if (blob_result.length > 0) + blob_result.length--; + // Avoid empty strings, which usually happen as padding at the end of the // stream. - if (blob_result.length > 0 && fits_in_pe(pe, offset, blob_result.length)) { set_sized_string( @@ -265,6 +1778,7 @@ STREAMS dotnet_parse_stream_headers( unsigned int i; memset(&headers, '\0', sizeof(STREAMS)); + headers.metadata_root = metadata_root; stream_header = (PSTREAM_HEADER) (pe->data + offset); @@ -315,7 +1829,7 @@ STREAMS dotnet_parse_stream_headers( headers.guid = stream_header; else if (strncmp(stream_name, "#Strings", 8) == 0 && headers.string == NULL) headers.string = stream_header; - else if (strncmp(stream_name, "#Blob", 5) == 0) + else if (strncmp(stream_name, "#Blob", 5) == 0 && headers.blob == NULL) headers.blob = stream_header; else if (strncmp(stream_name, "#US", 3) == 0 && headers.us == NULL) headers.us = stream_header; @@ -344,7 +1858,6 @@ void dotnet_parse_tilde_2( PE* pe, PTILDE_HEADER tilde_header, int64_t resource_base, - int64_t metadata_root, ROWS rows, INDEX_SIZES index_sizes, PSTREAMS streams) @@ -359,14 +1872,18 @@ void dotnet_parse_tilde_2( PCONSTANT_TABLE constant_table; DWORD resource_size, implementation; + // To save important data for future processing, initialize everything to 0 + TABLES tables = {0}; + char* name; char typelib[MAX_TYPELIB_SIZE + 1]; unsigned int i; int bit_check; int matched_bits = 0; + int64_t metadata_root = streams->metadata_root; int64_t resource_offset, field_offset; - uint32_t row_size, row_count, counter; + uint32_t row_size, row_count, counter, str_heap_size; const uint8_t* string_offset; const uint8_t* blob_offset; @@ -417,6 +1934,7 @@ void dotnet_parse_tilde_2( // Number of rows is the number of bits set to 1 in Valid. // Should use this technique: // http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan + // Count number of Rows size entries in header to skip over them for (i = 0; i < 64; i++) valid_rows += ((yr_le64toh(tilde_header->Valid) >> i) & 0x01); @@ -424,6 +1942,14 @@ void dotnet_parse_tilde_2( table_offset = (uint8_t*) row_offset; table_offset += sizeof(uint32_t) * valid_rows; + // Sometimes files have some sort of padding after, from DnSpy source + // it's denoted by EXTRA_DATA 0x40 flag in heapflags + // We then need to offset by 4 bytes, otherwise the analysis is wrong + // https://github.com/dnSpy/dnSpy/blob/2b6dcfaf602fb8ca6462b8b6237fdfc0c74ad994/dnSpy/dnSpy/Hex/Files/DotNet/TablesHeaderDataImpl.cs + // example: 1c2246af11000c3ce6b05ed6ba25060cbb00273c599428b98cf4013bdd82892f + if (tilde_header->HeapSizes & HEAP_EXTRA_DATA) + table_offset += 4; + #define DOTNET_STRING_INDEX(Name) \ index_sizes.string == 2 ? yr_le16toh(Name.Name_Short) \ : yr_le32toh(Name.Name_Long) @@ -431,6 +1957,8 @@ void dotnet_parse_tilde_2( string_offset = pe->data + metadata_root + yr_le32toh(streams->string->Offset); + str_heap_size = yr_le32toh(streams->string->Size); + // Now walk again this time parsing out what we care about. for (bit_check = 0; bit_check < 64; bit_check++) { @@ -468,13 +1996,21 @@ void dotnet_parse_tilde_2( break; name = pe_get_dotnet_string( - pe, string_offset, DOTNET_STRING_INDEX(module_table->Name)); + pe, + string_offset, + str_heap_size, + DOTNET_STRING_INDEX(module_table->Name)); if (name != NULL) set_string(name, pe->object, "module_name"); - table_offset += (2 + index_sizes.string + (index_sizes.guid * 3)) * - num_rows; + row_size = 2 + index_sizes.string + (index_sizes.guid * 3); + + tables.module.Offset = table_offset; + tables.module.RowCount = num_rows; + tables.module.RowSize = row_size; + + table_offset += row_size * num_rows; break; case BIT_TYPEREF: @@ -493,6 +2029,11 @@ void dotnet_parse_tilde_2( row_size = (index_size + (index_sizes.string * 2)); typeref_row_size = row_size; typeref_ptr = table_offset; + + tables.typeref.Offset = table_offset; + tables.typeref.RowCount = num_rows; + tables.typeref.RowSize = row_size; + table_offset += row_size * num_rows; break; @@ -508,9 +2049,14 @@ void dotnet_parse_tilde_2( else index_size = 2; - table_offset += (4 + (index_sizes.string * 2) + index_size + - index_sizes.field + index_sizes.methoddef) * - num_rows; + row_size = 4 + (index_sizes.string * 2) + index_size + index_sizes.field + + index_sizes.methoddef; + + tables.typedef_.Offset = table_offset; + tables.typedef_.RowCount = num_rows; + tables.typedef_.RowSize = row_size; + + table_offset += row_size * num_rows; break; case BIT_FIELDPTR: @@ -528,13 +2074,23 @@ void dotnet_parse_tilde_2( break; case BIT_METHODDEF: - table_offset += (4 + 2 + 2 + index_sizes.string + index_sizes.blob + - index_sizes.param) * - num_rows; + row_size = 4 + 2 + 2 + index_sizes.string + index_sizes.blob + + index_sizes.param; + + tables.methoddef.Offset = table_offset; + tables.methoddef.RowCount = num_rows; + tables.methoddef.RowSize = row_size; + table_offset += row_size * num_rows; break; case BIT_PARAM: - table_offset += (2 + 2 + index_sizes.string) * num_rows; + row_size = 2 + 2 + index_sizes.string; + + tables.param.Offset = table_offset; + tables.param.RowCount = num_rows; + tables.param.RowSize = row_size; + + table_offset += row_size * num_rows; break; case BIT_INTERFACEIMPL: @@ -549,7 +2105,13 @@ void dotnet_parse_tilde_2( else index_size = 2; - table_offset += (index_sizes.typedef_ + index_size) * num_rows; + row_size = index_sizes.typedef_ + index_size; + + tables.intefaceimpl.Offset = table_offset; + tables.intefaceimpl.RowCount = num_rows; + tables.intefaceimpl.RowSize = row_size; + + table_offset += row_size * num_rows; break; case BIT_MEMBERREF: @@ -597,7 +2159,7 @@ void dotnet_parse_tilde_2( constant_table = (PCONSTANT_TABLE) row_ptr; // Only look for constants of type string. - if (yr_le32toh(constant_table->Type) != ELEMENT_TYPE_STRING) + if (yr_le32toh(constant_table->Type) != TYPE_STRING) { row_ptr += row_size; continue; @@ -822,12 +2384,12 @@ void dotnet_parse_tilde_2( if (index_sizes.string == 4) { name = pe_get_dotnet_string( - pe, string_offset, *(DWORD*) typeref_row); + pe, string_offset, str_heap_size, *(DWORD*) typeref_row); } else { name = pe_get_dotnet_string( - pe, string_offset, *(WORD*) typeref_row); + pe, string_offset, str_heap_size, *(WORD*) typeref_row); } if (name != NULL && strncmp(name, "GuidAttribute", 13) != 0) @@ -1035,7 +2597,10 @@ void dotnet_parse_tilde_2( break; name = pe_get_dotnet_string( - pe, string_offset, DOTNET_STRING_INDEX(moduleref_table->Name)); + pe, + string_offset, + str_heap_size, + DOTNET_STRING_INDEX(moduleref_table->Name)); if (name != NULL) { @@ -1048,11 +2613,23 @@ void dotnet_parse_tilde_2( set_integer(counter, pe->object, "number_of_modulerefs"); - table_offset += (index_sizes.string) * num_rows; + row_size = index_sizes.string; + + tables.moduleref.Offset = table_offset; + tables.moduleref.RowCount = num_rows; + tables.moduleref.RowSize = row_size; + + table_offset += row_size * num_rows; break; case BIT_TYPESPEC: - table_offset += (index_sizes.blob) * num_rows; + row_size = index_sizes.blob; + + tables.typespec.Offset = table_offset; + tables.typespec.RowCount = num_rows; + tables.typespec.RowSize = row_size; + + table_offset += row_size * num_rows; break; case BIT_IMPLMAP: @@ -1142,12 +2719,14 @@ void dotnet_parse_tilde_2( name = pe_get_dotnet_string( pe, string_offset, + str_heap_size, yr_le32toh(*( DWORD*) (row_ptr + 4 + 2 + 2 + 2 + 2 + 4 + index_sizes.blob))); else name = pe_get_dotnet_string( pe, string_offset, + str_heap_size, yr_le16toh( *(WORD*) (row_ptr + 4 + 2 + 2 + 2 + 2 + 4 + index_sizes.blob))); @@ -1159,7 +2738,7 @@ void dotnet_parse_tilde_2( { name = pe_get_dotnet_string( pe, - string_offset, + string_offset, str_heap_size, yr_le32toh(*(DWORD*) ( row_ptr + 4 + 2 + 2 + 2 + 2 + 4 + index_sizes.blob + @@ -1169,7 +2748,7 @@ void dotnet_parse_tilde_2( { name = pe_get_dotnet_string( pe, - string_offset, + string_offset, str_heap_size, yr_le16toh(*(WORD*) ( row_ptr + 4 + 2 + 2 + 2 + 2 + 4 + index_sizes.blob + @@ -1265,12 +2844,14 @@ void dotnet_parse_tilde_2( name = pe_get_dotnet_string( pe, string_offset, + str_heap_size, yr_le32toh( *(DWORD*) (row_ptr + 2 + 2 + 2 + 2 + 4 + index_sizes.blob))); else name = pe_get_dotnet_string( pe, string_offset, + str_heap_size, yr_le16toh( *(WORD*) (row_ptr + 2 + 2 + 2 + 2 + 4 + index_sizes.blob))); @@ -1280,6 +2861,10 @@ void dotnet_parse_tilde_2( row_ptr += row_size; } + tables.assemblyref.Offset = table_offset; + tables.assemblyref.RowCount = num_rows; + tables.assemblyref.RowSize = row_size; + set_integer(i, pe->object, "number_of_assembly_refs"); table_offset += row_size * num_rows; break; @@ -1383,6 +2968,7 @@ void dotnet_parse_tilde_2( name = pe_get_dotnet_string( pe, string_offset, + str_heap_size, DOTNET_STRING_INDEX(manifestresource_table->Name)); if (name != NULL) @@ -1398,7 +2984,13 @@ void dotnet_parse_tilde_2( break; case BIT_NESTEDCLASS: - table_offset += (index_sizes.typedef_ * 2) * num_rows; + row_size = index_sizes.typedef_ * 2; + + tables.nestedclass.Offset = table_offset; + tables.nestedclass.RowCount = num_rows; + tables.nestedclass.RowSize = row_size; + + table_offset += row_size * num_rows; break; case BIT_GENERICPARAM: @@ -1410,7 +3002,13 @@ void dotnet_parse_tilde_2( else index_size = 2; - table_offset += (2 + 2 + index_size + index_sizes.string) * num_rows; + row_size = (2 + 2 + index_size + index_sizes.string); + + tables.genericparam.Offset = table_offset; + tables.genericparam.RowCount = num_rows; + tables.genericparam.RowSize = row_size; + + table_offset += row_size * num_rows; break; case BIT_METHODSPEC: @@ -1447,6 +3045,17 @@ void dotnet_parse_tilde_2( matched_bits++; } + + CLASS_CONTEXT class_context = { + .pe = pe, + .tables = &tables, + .index_sizes = &index_sizes, + .str_heap = string_offset, + .str_size = str_heap_size, + .blob_heap = pe->data + streams->metadata_root + streams->blob->Offset, + .blob_size = streams->blob->Size}; + + parse_user_types(&class_context); } // Parsing the #~ stream is done in two parts. The first part (this function) @@ -1454,14 +3063,11 @@ void dotnet_parse_tilde_2( // particular it is collecting the number of rows for each of the tables. The // second part parses the actual tables of interest. -void dotnet_parse_tilde( - PE* pe, - int64_t metadata_root, - PCLI_HEADER cli_header, - PSTREAMS streams) +void dotnet_parse_tilde(PE* pe, PCLI_HEADER cli_header, PSTREAMS streams) { PTILDE_HEADER tilde_header; int64_t resource_base; + int64_t metadata_root = streams->metadata_root; uint32_t* row_offset = NULL; int bit_check; @@ -1531,7 +3137,7 @@ void dotnet_parse_tilde( switch (bit_check) { case BIT_MODULE: - ROW_CHECK(module); + ROW_CHECK_WITH_INDEX(module); break; case BIT_MODULEREF: ROW_CHECK_WITH_INDEX(moduleref); @@ -1543,7 +3149,7 @@ void dotnet_parse_tilde( ROW_CHECK_WITH_INDEX(assemblyrefprocessor); break; case BIT_TYPEREF: - ROW_CHECK(typeref); + ROW_CHECK_WITH_INDEX(typeref); break; case BIT_METHODDEF: ROW_CHECK_WITH_INDEX(methoddef); @@ -1555,7 +3161,7 @@ void dotnet_parse_tilde( ROW_CHECK_WITH_INDEX(typedef_); break; case BIT_TYPESPEC: - ROW_CHECK(typespec); + ROW_CHECK_WITH_INDEX(typespec); break; case BIT_FIELD: ROW_CHECK_WITH_INDEX(field); @@ -1567,7 +3173,7 @@ void dotnet_parse_tilde( ROW_CHECK_WITH_INDEX(property); break; case BIT_INTERFACEIMPL: - ROW_CHECK(interfaceimpl); + ROW_CHECK_WITH_INDEX(interfaceimpl); break; case BIT_EVENT: ROW_CHECK_WITH_INDEX(event); @@ -1576,7 +3182,7 @@ void dotnet_parse_tilde( ROW_CHECK(standalonesig); break; case BIT_ASSEMBLY: - ROW_CHECK(assembly); + ROW_CHECK_WITH_INDEX(assembly); break; case BIT_FILE: ROW_CHECK(file); @@ -1594,7 +3200,7 @@ void dotnet_parse_tilde( ROW_CHECK(genericparamconstraint); break; case BIT_METHODSPEC: - ROW_CHECK(methodspec); + ROW_CHECK_WITH_INDEX(methodspec); break; default: break; @@ -1608,13 +3214,7 @@ void dotnet_parse_tilde( pe, yr_le32toh(cli_header->Resources.VirtualAddress)); dotnet_parse_tilde_2( - pe, - tilde_header, - resource_base, - metadata_root, - rows, - index_sizes, - streams); + pe, tilde_header, resource_base, rows, index_sizes, streams); } static bool dotnet_is_dotnet(PE* pe) @@ -1758,7 +3358,7 @@ void dotnet_parse_com(PE* pe) // These tables reference the blob and string streams, so we need to ensure // those are not NULL also. if (headers.tilde != NULL && headers.string != NULL && headers.blob != NULL) - dotnet_parse_tilde(pe, metadata_root, cli_header, &headers); + dotnet_parse_tilde(pe, cli_header, &headers); if (headers.us != NULL) dotnet_parse_us(pe, metadata_root, headers.us); @@ -1788,6 +3388,47 @@ begin_declarations declare_integer("number_of_resources"); + begin_struct_array("classes") + declare_string("fullname"); + declare_string("name"); + declare_string("namespace"); + declare_string("visibility"); + declare_string("type"); + declare_integer("abstract"); + declare_integer("sealed"); + + declare_integer("number_of_generic_parameters"); + declare_string_array("generic_parameters"); + + declare_integer("number_of_base_types"); + declare_string_array("base_types"); + + declare_integer("number_of_methods"); + begin_struct_array("methods") + declare_string_array("generic_parameters"); + + declare_integer("number_of_generic_parameters"); + + begin_struct_array("parameters") + declare_string("name"); + declare_string("type"); + end_struct_array("parameters") + + declare_integer("number_of_parameters"); + + declare_string("return_type"); + declare_integer("abstract"); + declare_integer("final"); + declare_integer("virtual"); + declare_integer("static"); + declare_string("visibility"); + declare_string("name"); + end_struct_array("methods") + + end_struct_array("classes") + + declare_integer("number_of_classes"); + begin_struct_array("assembly_refs") begin_struct("version") declare_integer("major"); diff --git a/libyara/simple_str.c b/libyara/simple_str.c index b8488c08..2f4be464 100644 --- a/libyara/simple_str.c +++ b/libyara/simple_str.c @@ -1,3 +1,32 @@ +/* +Copyright (c) 2022. The YARA Authors. All Rights Reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + #include #include #include diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 86b688da..c1ea88e5 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -114,6 +114,7 @@ cc_test( "data/079a472d22290a94ebb212aa8015cdc8dd28a968c6b4d3b88acdd58ce2d3b885", "data/079a472d22290a94ebb212aa8015cdc8dd28a968c6b4d3b88acdd58ce2d3b885.upx", "data/3b8b90159fa9b6048cc5410c5d53f116943564e4d05b04a843f9b3d0540d0c1c", + "data/ChipTune.efi", "data/ca21e1c32065352d352be6cde97f89c141d7737ea92434831f998080783d5386", "data/mtxex.dll", "data/mtxex_modified_rsrc_rva.dll", @@ -122,10 +123,9 @@ cc_test( "data/tiny", "data/tiny-idata-51ff", "data/tiny-idata-5200", + "data/tiny-macho", "data/tiny-overlay", "data/weird_rich", - "data/ChipTune.efi", - "data/tiny-macho" ], linkstatic = True, deps = [ @@ -239,9 +239,10 @@ cc_test( srcs = ["test-dotnet.c"], copts = COPTS, data = [ - "data/tiny", "data/0ca09bde7602769120fadc4f7a4147347a7a97271370583586c9e587fd396171", + "data/756684f4017ba7e931a26724ae61606b16b5f8cc84ed38a260a34e50c5016f59", "data/bad_dotnet_pe", + "data/tiny", ], linkstatic = True, deps = [ diff --git a/tests/data/756684f4017ba7e931a26724ae61606b16b5f8cc84ed38a260a34e50c5016f59 b/tests/data/756684f4017ba7e931a26724ae61606b16b5f8cc84ed38a260a34e50c5016f59 new file mode 100644 index 00000000..b5a37e00 Binary files /dev/null and b/tests/data/756684f4017ba7e931a26724ae61606b16b5f8cc84ed38a260a34e50c5016f59 differ diff --git a/tests/test-dotnet.c b/tests/test-dotnet.c index dc6703cc..19b7fa43 100644 --- a/tests/test-dotnet.c +++ b/tests/test-dotnet.c @@ -84,6 +84,54 @@ int main(int argc, char** argv) "tests/data/" "0ca09bde7602769120fadc4f7a4147347a7a97271370583586c9e587fd396171"); + assert_true_rule_file( + "import \"dotnet\" \ + rule test { \ + condition: \ + dotnet.classes[0].fullname == \"Autodesk.AutoCAD.Internal.Windows.MenuServices\" and \ + dotnet.classes[0].name == \"MenuServices\" and \ + dotnet.classes[0].namespace == \"Autodesk.AutoCAD.Internal.Windows\" and \ + dotnet.classes[0].visibility == \"public\" and \ + dotnet.classes[0].type == \"class\" and \ + dotnet.classes[0].abstract and \ + dotnet.classes[0].sealed and \ + dotnet.classes[0].number_of_generic_parameters == 0 and \ + dotnet.classes[0].number_of_base_types == 1 and \ + dotnet.classes[0].base_types[0] == \"System.Object\" and \ + dotnet.classes[0].number_of_methods == 20 and \ + dotnet.classes[0].methods[0].name == \"Initialize\" and \ + dotnet.classes[0].methods[0].visibility == \"public\" and \ + dotnet.classes[0].methods[0].static and \ + not dotnet.classes[0].methods[0].virtual and \ + not dotnet.classes[0].methods[0].final and \ + dotnet.classes[0].methods[0].return_type == \"void\" and \ + dotnet.classes[0].methods[0].parameters[0].name == \"productName\" and \ + dotnet.classes[0].methods[0].parameters[0].type == \"string\" and \ + dotnet.classes[0].methods[0].parameters[1].name == \"maxRecentFiles\" and \ + dotnet.classes[0].methods[0].parameters[1].type == \"int\" and \ + dotnet.classes[0].methods[0].number_of_generic_parameters == 0 and \ + dotnet.classes[156].fullname == \"Autodesk.AutoCAD.Ribbon.Point3dDoubleToStringConverter\" and \ + dotnet.classes[156].name == \"Point3dDoubleToStringConverter\" and \ + dotnet.classes[156].namespace == \"Autodesk.AutoCAD.Ribbon\" and \ + dotnet.classes[156].number_of_base_types == 2 and \ + dotnet.classes[156].base_types[0] == \"System.Object\" and \ + dotnet.classes[156].base_types[1] == \"System.Windows.Data.IValueConverter\" and \ + dotnet.classes[156].number_of_methods == 3 and \ + dotnet.classes[156].methods[0].name == \"Convert\" and \ + dotnet.classes[156].methods[0].return_type == \"object\" and \ + dotnet.classes[156].methods[0].number_of_parameters == 4 and \ + dotnet.classes[156].methods[0].parameters[0].name == \"value\" and \ + dotnet.classes[156].methods[0].parameters[0].type == \"object\" and \ + dotnet.classes[156].methods[0].parameters[1].name == \"targetType\" and \ + dotnet.classes[156].methods[0].parameters[1].type == \"System.Type\" and \ + dotnet.classes[156].methods[0].parameters[2].name == \"parameter\" and \ + dotnet.classes[156].methods[0].parameters[2].type == \"object\" and \ + dotnet.classes[156].methods[0].parameters[3].name == \"culture\" and \ + dotnet.classes[156].methods[0].parameters[3].type == \"System.Globalization.CultureInfo\" \ + }", + "tests/data/" + "756684f4017ba7e931a26724ae61606b16b5f8cc84ed38a260a34e50c5016f59"); + assert_false_rule( "import \"dotnet\" \ rule test { \