diff --git a/scripts/base/files/pe/main.bro b/scripts/base/files/pe/main.bro index db4d9e41d4..8577d24078 100644 --- a/scripts/base/files/pe/main.bro +++ b/scripts/base/files/pe/main.bro @@ -1,48 +1,75 @@ - module PE; export { redef enum Log::ID += { LOG }; type Info: record { + ## Current timestamp. ts: time &log; - fuid: string &log; + + ## File id of this portable executable file. + id: string &log; + + ## The target machine that the file was compiled for. machine: string &log &optional; + + ## The time that the file was created at. compile_ts: time &log &optional; + + ## The required operating system. os: string &log &optional; + + ## The subsystem that is required to run this file. subsystem: string &log &optional; - is_exe: bool &log &default=F; - is_dll: bool &log &default=F; + ## Is the file an executable, or just an object file? + is_exe: bool &log &default=T; + + ## Is the file a 64-bit executable? is_64bit: bool &log &default=T; + ## Does the file support Address Space Layout Randomization? uses_aslr: bool &log &default=F; + + ## Does the file support Data Execution Prevention? uses_dep: bool &log &default=F; + + ## Does the file enforce code integrity checks? uses_code_integrity: bool &log &default=F; + + ## Does the file use structured exception handing? uses_seh: bool &log &default=T; + ## Does the file have an import table? has_import_table: bool &log &optional; + + ## Does the file have an export table? has_export_table: bool &log &optional; + + ## Does the file have an attribute certificate table? has_cert_table: bool &log &optional; + + ## Does the file have a debug table? has_debug_data: bool &log &optional; + ## The names of the sections, in order. section_names: vector of string &log &optional; }; + ## Event for accessing logged records. + global log_pe: event(rec: Info); + + ## A hook that gets called when we first see a PE file. global set_file: hook(f: fa_file); } -redef record Info += { - confirmed: bool &default=F; -}; - redef record fa_file += { pe: Info &optional; }; event bro_init() &priority=5 { - Log::create_stream(LOG, [$columns=Info]); + Log::create_stream(LOG, [$columns=Info, $ev=log_pe]); } hook set_file(f: fa_file) &priority=5 @@ -50,7 +77,7 @@ hook set_file(f: fa_file) &priority=5 if ( ! f?$pe ) { local c: set[string] = set(); - f$pe = [$ts=network_time(), $fuid=f$id]; + f$pe = [$ts=network_time(), $id=f$id]; } } @@ -62,26 +89,20 @@ event pe_dos_header(f: fa_file, h: PE::DOSHeader) &priority=5 event pe_file_header(f: fa_file, h: PE::FileHeader) &priority=5 { hook set_file(f); + f$pe$is_exe = h$optional_header_size > 0; f$pe$compile_ts = h$ts; f$pe$machine = machine_types[h$machine]; for ( c in h$characteristics ) { - if ( c == 0x2 ) - f$pe$is_exe = T; if ( c == 0x100 ) f$pe$is_64bit = F; - if ( c == 0x2000 ) - f$pe$is_dll = T; } } event pe_optional_header(f: fa_file, h: PE::OptionalHeader) &priority=5 { hook set_file(f); - - if ( h$magic == 0x10b || h$magic == 0x20b ) - f$pe$confirmed = T; - else + if ( ! f$pe$is_exe ) return; f$pe$os = os_versions[h$os_version_major, h$os_version_minor]; @@ -98,15 +119,17 @@ event pe_optional_header(f: fa_file, h: PE::OptionalHeader) &priority=5 f$pe$uses_seh = F; } - f$pe$has_export_table = (|h$rvas| > 0 && h$rvas[0] > 0); - f$pe$has_import_table = (|h$rvas| > 1 && h$rvas[1] > 0); - f$pe$has_cert_table = (|h$rvas| > 4 && h$rvas[4] > 0); - f$pe$has_debug_data = (|h$rvas| > 6 && h$rvas[6] > 0); + f$pe$has_export_table = (|h$table_sizes| > 0 && h$table_sizes[0] > 0); + f$pe$has_import_table = (|h$table_sizes| > 1 && h$table_sizes[1] > 0); + f$pe$has_cert_table = (|h$table_sizes| > 4 && h$table_sizes[4] > 0); + f$pe$has_debug_data = (|h$table_sizes| > 6 && h$table_sizes[6] > 0); } event pe_section_header(f: fa_file, h: PE::SectionHeader) &priority=5 { hook set_file(f); + if ( ! f$pe$is_exe ) + return; if ( ! f$pe?$section_names ) f$pe$section_names = vector(); @@ -115,7 +138,7 @@ event pe_section_header(f: fa_file, h: PE::SectionHeader) &priority=5 event file_state_remove(f: fa_file) &priority=-5 { - if ( f?$pe && f$pe$confirmed ) + if ( f?$pe ) Log::write(LOG, f$pe); } diff --git a/scripts/base/init-bare.bro b/scripts/base/init-bare.bro index 3babb1ded5..8034d1ec67 100644 --- a/scripts/base/init-bare.bro +++ b/scripts/base/init-bare.bro @@ -2550,75 +2550,138 @@ type irc_join_list: set[irc_join_info]; module PE; export { type PE::DOSHeader: record { + ## The magic number of a portable executable file ("MZ"). signature : string; + ## The number of bytes in the last page that are used. used_bytes_in_last_page : count; + ## The number of pages in the file that are part of the PE file itself. file_in_pages : count; + ## Number of relocation entries stored after the header. num_reloc_items : count; + ## Number of paragraphs in the header. header_in_paragraphs : count; + ## Number of paragraps of additional memory that the program will need. min_extra_paragraphs : count; + ## Maximum number of paragraphs of additional memory. max_extra_paragraphs : count; + ## Relative value of the stack segment. init_relative_ss : count; + ## Initial value of the SP register. init_sp : count; + ## Checksum. The 16-bit sum of all words in the file should be 0. Normally not set. checksum : count; + ## Initial value of the IP register. init_ip : count; + ## Initial value of the CS register (relative to the initial segment). init_relative_cs : count; + ## Offset of the first relocation table. addr_of_reloc_table : count; + ## Overlays allow you to append data to the end of the file. If this is the main program, + ## this will be 0. overlay_num : count; + ## OEM identifier. oem_id : count; + ## Additional OEM info, specific to oem_id. oem_info : count; + ## Address of the new EXE header. addr_of_new_exe_header : count; }; type PE::FileHeader: record { - machine : count; - ts : time; - sym_table_ptr : count; - num_syms : count; - characteristics : set[count]; + ## The target machine that the file was compiled for. + machine : count; + ## The time that the file was created at. + ts : time; + ## Pointer to the symbol table. + sym_table_ptr : count; + ## Number of symbols. + num_syms : count; + ## The size of the optional header. + optional_header_size : count; + ## Bit flags that determine if this file is executable, non-relocatable, and/or a DLL. + characteristics : set[count]; }; type PE::OptionalHeader: record { + ## PE32 or PE32+ indicator. magic : count; + ## The major version of the linker used to create the PE. major_linker_version : count; + ## The minor version of the linker used to create the PE. minor_linker_version : count; + ## Size of the .text section. size_of_code : count; + ## Size of the .data section. size_of_init_data : count; + ## Size of the .bss section. size_of_uninit_data : count; + ## The relative virtual address (RVA) of the entry point. addr_of_entry_point : count; + ## The relative virtual address (RVA) of the .text section. base_of_code : count; + ## The relative virtual address (RVA) of the .data section. base_of_data : count &optional; + ## Preferred memory location for the image to be based at. image_base : count; + ## The alignment (in bytes) of sections when they're loaded in memory. section_alignment : count; + ## The alignment (in bytes) of the raw data of sections. file_alignment : count; + ## The major version of the required OS. os_version_major : count; + ## The minor version of the required OS. os_version_minor : count; + ## The major version of this image. major_image_version : count; + ## The minor version of this image. minor_image_version : count; + ## The major version of the subsystem required to run this file. major_subsys_version : count; + ## The minor version of the subsystem required to run this file. minor_subsys_version : count; - win32_version : count; + ## The size (in bytes) of the iamge as the image is loaded in memory. size_of_image : count; + ## The size (in bytes) of the headers, rounded up to file_alignment. size_of_headers : count; + ## The image file checksum. checksum : count; + ## The subsystem that's required to run this image. subsystem : count; + ## Bit flags that determine how to execute or load this file. dll_characteristics : set[count]; - loader_flags : count; - rvas : vector of count; + ## A vector with the sizes of various tables and strings that are + ## defined in the optional header data directories. Examples include + ## the import table, the resource table, and debug information. + table_sizes : vector of count; }; ## Record for Portable Executable (PE) section headers. type PE::SectionHeader: record { - name : string; - virtual_size : count; - virtual_addr : count; - size_of_raw_data : count; - ptr_to_raw_data : count; - non_used_ptr_to_relocs : count; - non_used_ptr_to_line_nums : count; - non_used_num_of_relocs : count; - non_used_num_of_line_nums : count; - characteristics : set[count]; + ## The name of the section + name : string; + ## The total size of the section when loaded into memory. + virtual_size : count; + ## The relative virtual address (RVA) of the section. + virtual_addr : count; + ## The size of the initialized data for the section, as it is + ## in the file on disk. + size_of_raw_data : count; + ## The virtual address of the initialized dat for the section, + ## as it is in the file on disk. + ptr_to_raw_data : count; + ## The file pointer to the beginning of relocation entries for + ## the section. + ptr_to_relocs : count; + ## The file pointer to the beginning of line-number entries for + ## the section. + ptr_to_line_nums : count; + ## The number of relocation entries for the section. + num_of_relocs : count; + ## The number of line-number entrie for the section. + num_of_line_nums : count; + ## Bit-flags that describe the characteristics of the section. + characteristics : set[count]; }; } module GLOBAL; diff --git a/src/file_analysis/analyzer/pe/events.bif b/src/file_analysis/analyzer/pe/events.bif index 3e6bbf8faf..c804937c49 100644 --- a/src/file_analysis/analyzer/pe/events.bif +++ b/src/file_analysis/analyzer/pe/events.bif @@ -1,11 +1,57 @@ +## A :abbr:`PE (Portable Executable)` file DOS header was parsed. +## This is the top-level header and contains information like the +## size of the file, initial value of registers, etc. +## +## f: The file. +## +## h: The parsed DOS header information. +## +## .. bro:see:: pe_dos_code pe_file_header pe_optional_header pe_section_header event pe_dos_header%(f: fa_file, h: PE::DOSHeader%); +## A :abbr:`PE (Portable Executable)` file DOS stub was parsed. +## The stub is a valid application that runs under MS-DOS, by default +## to inform the user that the program can't be run in DOS mode. +## +## f: The file. +## +## code: The DOS stub +## +## .. bro:see:: pe_dos_header pe_file_header pe_optional_header pe_section_header event pe_dos_code%(f: fa_file, code: string%); +## A :abbr:`PE (Portable Executable)` file file header was parsed. +## This header contains information like the target machine, +## the timestamp when the file was created, the number of sections, and +## pointers to other parts of the file. +## +## f: The file. +## +## h: The parsed file header information. +## +## .. bro:see:: pe_dos_header pe_dos_code pe_optional_header pe_section_header event pe_file_header%(f: fa_file, h: PE::FileHeader%); +## A :abbr:`PE (Portable Executable)` file optional header was parsed. +## This header is required for executable files, but not for object files. +## It contains information like OS requirements to execute the file, the +## original entry point address, and information needed to load the file +## into memory. +## +## f: The file. +## +## h: The parsed optional header information. +## +## .. bro:see:: pe_dos_header pe_dos_code pe_file_header pe_section_header event pe_optional_header%(f: fa_file, h: PE::OptionalHeader%); +## A :abbr:`PE (Portable Executable)` file section header was parsed. +## This header contains information like the section name, size, address, +## and characteristics. +## +## f: The file. +## +## h: The parsed section header information. +## +## .. bro:see:: pe_dos_header pe_dos_code pe_file_header pe_optional_header event pe_section_header%(f: fa_file, h: PE::SectionHeader%); - -event pe_import_entry%(f: fa_file, m: string, name: string%); \ No newline at end of file diff --git a/src/file_analysis/analyzer/pe/pe-analyzer.pac b/src/file_analysis/analyzer/pe/pe-analyzer.pac index fd3ee5b0e2..874a142ba8 100644 --- a/src/file_analysis/analyzer/pe/pe-analyzer.pac +++ b/src/file_analysis/analyzer/pe/pe-analyzer.pac @@ -98,7 +98,8 @@ refine flow File += { fh->Assign(1, new Val(static_cast(${h.TimeDateStamp}), TYPE_TIME)); fh->Assign(2, new Val(${h.PointerToSymbolTable}, TYPE_COUNT)); fh->Assign(3, new Val(${h.NumberOfSymbols}, TYPE_COUNT)); - fh->Assign(4, characteristics_to_bro(${h.Characteristics}, 16)); + fh->Assign(4, new Val(${h.SizeOfOptionalHeader}, TYPE_COUNT)); + fh->Assign(5, characteristics_to_bro(${h.Characteristics}, 16)); BifEvent::generate_pe_file_header((analyzer::Analyzer *) connection()->bro_analyzer(), connection()->bro_analyzer()->GetFile()->GetVal()->Ref(), fh); @@ -142,15 +143,13 @@ refine flow File += { oh->Assign(15, new Val(${h.minor_image_version}, TYPE_COUNT)); oh->Assign(16, new Val(${h.minor_subsys_version}, TYPE_COUNT)); oh->Assign(17, new Val(${h.minor_subsys_version}, TYPE_COUNT)); - oh->Assign(18, new Val(${h.win32_version}, TYPE_COUNT)); - oh->Assign(19, new Val(${h.size_of_image}, TYPE_COUNT)); - oh->Assign(20, new Val(${h.size_of_headers}, TYPE_COUNT)); - oh->Assign(21, new Val(${h.checksum}, TYPE_COUNT)); - oh->Assign(22, new Val(${h.subsystem}, TYPE_COUNT)); - oh->Assign(23, characteristics_to_bro(${h.dll_characteristics}, 16)); - oh->Assign(24, new Val(${h.loader_flags}, TYPE_COUNT)); + oh->Assign(18, new Val(${h.size_of_image}, TYPE_COUNT)); + oh->Assign(19, new Val(${h.size_of_headers}, TYPE_COUNT)); + oh->Assign(20, new Val(${h.checksum}, TYPE_COUNT)); + oh->Assign(21, new Val(${h.subsystem}, TYPE_COUNT)); + oh->Assign(22, characteristics_to_bro(${h.dll_characteristics}, 16)); - oh->Assign(25, process_rvas(${h.rvas}, ${h.number_of_rva_and_sizes})); + oh->Assign(23, process_rvas(${h.rvas}, ${h.number_of_rva_and_sizes})); BifEvent::generate_pe_optional_header((analyzer::Analyzer *) connection()->bro_analyzer(), connection()->bro_analyzer()->GetFile()->GetVal()->Ref(), diff --git a/src/file_analysis/analyzer/pe/pe-file-headers.pac b/src/file_analysis/analyzer/pe/pe-file-headers.pac index adf0ce575d..a3d46dc72e 100644 --- a/src/file_analysis/analyzer/pe/pe-file-headers.pac +++ b/src/file_analysis/analyzer/pe/pe-file-headers.pac @@ -39,9 +39,14 @@ type DOS_Code(len: uint32) = record { type NT_Headers = record { PESignature : uint32; file_header : File_Header; - optional_header : Optional_Header &length=file_header.SizeOfOptionalHeader; + have_opt_header : case file_header.SizeOfOptionalHeader of { + 0 -> none: empty; + default -> optional_header : Optional_Header &length=file_header.SizeOfOptionalHeader; + }; } &let { - length: uint32 = file_header.SizeOfOptionalHeader+offsetof(optional_header); + length: uint32 = file_header.SizeOfOptionalHeader + offsetof(have_opt_header); + is_exe: bool = file_header.SizeOfOptionalHeader > 0; + size_of_headers: uint32 = is_exe ? optional_header.size_of_headers : 0; } &length=length; # The file header is mainly self-describing diff --git a/src/file_analysis/analyzer/pe/pe-file-idata.pac b/src/file_analysis/analyzer/pe/pe-file-idata.pac new file mode 100644 index 0000000000..589bcc68ad --- /dev/null +++ b/src/file_analysis/analyzer/pe/pe-file-idata.pac @@ -0,0 +1,183 @@ +## Support for parsing the .idata section + +type import_directory = record { + rva_import_lookup_table : uint32; + time_date_stamp : uint32; + forwarder_chain : uint32; + rva_module_name : uint32; + rva_import_addr_table : uint32; +} &let { + is_null: bool = rva_module_name == 0; + proc: bool = $context.connection.proc_image_import_directory(this); +} &length=20; + +type import_lookup_attrs(pe32_format: uint8) = record { + is_pe32_plus: case pe32_format of { + PE32_PLUS -> attrs_64: uint64; + default -> attrs_32: uint32; + }; +} &let { + import_by_ordinal: bool = (pe32_format == PE32_PLUS) ? (attrs_64 & 0x8000000000000000) > 1: (attrs_32 & 0x80000000) > 1; + attrs: uint64 = (pe32_format == PE32_PLUS) ? attrs_64 : attrs_32; + ordinal: uint16 = attrs & 0xff; + hint_rva: uint32 = attrs & 0xffff; + proc9000: bool = $context.connection.proc_import_lookup_attrs(this); +} &length=(pe32_format == PE32_PLUS ? 8 : 4); + +type import_lookup_table = record { + attrs: import_lookup_attrs($context.connection.get_pe32_format())[] &until($element.attrs == 0); +} &let { + proc: bool = $context.connection.proc_import_lookup_table(this); +}; + +type import_entry(is_module: bool, pad_align: uint8) = record { + pad: bytestring &length=pad_align; + has_index: case is_module of { + true -> null: empty; + false -> index: uint16; + }; + name: null_terminated_string; +} &let { + proc_align: bool = $context.connection.proc_import_hint(name, is_module); +}; + +type idata = record { + directory_table : import_directory[] &until $element.is_null; + lookup_tables : import_lookup_table[] &until $context.connection.get_num_imports() <= 0; + hint_table : import_entry($context.connection.get_next_hint_type(), $context.connection.get_next_hint_align())[] &until($context.connection.imports_done()); +}; + +refine typeattr RVAS += &let { + proc_import_table: bool = $context.connection.proc_idata_rva(rvas[1]) &if (num > 1); +}; + +refine connection MockConnection += { + %member{ + uint8 num_imports_; // How many import tables will we have? + + uint32 import_table_rva_; // Used for finding the right section + uint32 import_table_va_; + uint32 import_table_len_; + + // We need to track the number of imports for each, to + // know when we've parsed them all. + vector imports_per_module_; + + // These are to determine the alignment of the import hints + uint32 next_hint_index_; + uint8 next_hint_align_; + bool next_hint_is_module_; + + // Track the module name, so we know what each import's for + bytestring module_name_; + %} + + %init{ + // It ends with a null import entry, so we'll set it to -1. + num_imports_ = -1; + + // First hint is a module name. + next_hint_is_module_ = true; + next_hint_index_ = 0; + next_hint_align_ = 0; + + module_name_ = bytestring(); + %} + + %cleanup{ + module_name_.free(); + %} + + # When we read the section header, store the relative virtual address and + # size of the .idata section, so we know when we get there. + function proc_idata_rva(r: RVA): bool + %{ + import_table_rva_ = ${r.virtual_address}; + import_table_len_ = ${r.size}; + + return true; + %} + + # Each import directory means another module we're importing from. + function proc_image_import_directory(i: import_directory): bool + %{ + printf("Parsed import directory. name@%x, IAT@%x\n", ${i.rva_module_name}, ${i.rva_import_addr_table}); + num_imports_++; + return true; + %} + + # Store the number of functions imported in each module lookup table. + function proc_import_lookup_table(t: import_lookup_table): bool + %{ + --num_imports_; + imports_per_module_.push_back(${t.attrs}->size()); + return true; + %} + + function proc_import_lookup_attrs(t: import_lookup_attrs): bool + %{ + printf("Parsed import lookup attrs. Hints @%x\n", ${t.hint_rva}); + return true; + %} + + # We need to calculate the length of the next padding field + function proc_import_hint(hint_name: bytestring, is_module: bool): bool + %{ + printf("Parsed import hint\n"); + next_hint_align_ = ${hint_name}.length() % 2; + if ( is_module && ${hint_name}.length() > 1 ) + { + module_name_.clear(); + module_name_.init(${hint_name}.data(), ${hint_name}.length() - 1); + } + + return true; + %} + + # Functions have an index field, modules don't. Which one is this? + function get_next_hint_type(): bool + %{ + if ( next_hint_is_module_ ) + { + next_hint_is_module_ = false; + return true; + } + if ( --imports_per_module_[next_hint_index_] == 0) + { + ++next_hint_index_; + return true; + } + return false; + %} + + function imports_done(): bool + %{ + return next_hint_index_ == imports_per_module_.size(); + %} + + function get_module_name(): bytestring + %{ + return module_name_; + %} + + function get_import_table_addr(): uint32 + %{ + return import_table_va_ > 0 ? import_table_va_ : 0; + %} + + function get_import_table_len(): uint32 + %{ + return import_table_va_ > 0 ? import_table_len_ : 0; + %} + + function get_num_imports(): uint8 + %{ + return num_imports_; + %} + + function get_next_hint_align(): uint8 + %{ + return next_hint_align_; + %} + +}; \ No newline at end of file diff --git a/src/file_analysis/analyzer/pe/pe-file.pac b/src/file_analysis/analyzer/pe/pe-file.pac index 64902e9e9f..0cb308b17e 100644 --- a/src/file_analysis/analyzer/pe/pe-file.pac +++ b/src/file_analysis/analyzer/pe/pe-file.pac @@ -11,8 +11,8 @@ type Portable_Executable = record { headers : Headers; pad : Padding(restofdata); } &let { - unparsed_hdr_len: uint32 = headers.pe_header.optional_header.size_of_headers - headers.length; - restofdata: uint64 = $context.connection.get_max_file_location() - headers.pe_header.optional_header.size_of_headers + unparsed_hdr_len; + unparsed_hdr_len: uint32 = headers.pe_header.size_of_headers - headers.length; + restofdata: uint64 = headers.pe_header.is_exe ? $context.connection.get_max_file_location() - headers.pe_header.size_of_headers + unparsed_hdr_len : 0; proc: bool = $context.connection.proc_pe(this); } &byteorder=littleendian;