Merge remote-tracking branch 'origin/topic/vladg/file-analysis-exe-analyzer'

* origin/topic/vladg/file-analysis-exe-analyzer: (31 commits)
  Tweak the PE OS versions based on real-world traffic.
  Update pe/main.bro to user register_for_mime_types, ensuring it will also work with the upcoming Files framework changes.
  A bit of final core-level cleanup.
  A bit of final script cleanup.
  Update baselines.
  Add a btest for the PE analyzer.
  Add a PE memleak test, and fix a memleak.
  Documentation and a bit of overall cleanup.
  Add data about which tables are present.
  Remove the .idata parsing, as it can be more complicated in some cases.
  Fix a PE analyzer failure where the IAT isn't aligned with a section boundary.
  PE: Rehash the log a bit.
  Make base_of_data optional.
  Fix support for PE32+ files.
  PE Analyzer cleanup.
  Checkpoint - Import Address Table being parsed.
  Some changes to fix PE analyzer on master.
  Parse PE section headers.
  Updated PE analyzer to work with changes in master.
  In progress checkpoint.  Things are starting to work.
  ...

BIT-1369 #merged
This commit is contained in:
Robin Sommer 2015-04-20 19:15:23 -07:00
commit a9979d56a4
30 changed files with 1429 additions and 53 deletions

View file

@ -1,5 +1,6 @@
add_subdirectory(data_event)
add_subdirectory(extract)
add_subdirectory(hash)
add_subdirectory(pe)
add_subdirectory(unified2)
add_subdirectory(x509)

View file

@ -0,0 +1,10 @@
include(BroPlugin)
include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR})
bro_plugin_begin(Bro PE)
bro_plugin_cc(PE.cc Plugin.cc)
bro_plugin_bif(events.bif)
bro_plugin_pac(pe.pac pe-file.pac pe-analyzer.pac)
bro_plugin_end()

View file

@ -0,0 +1,39 @@
#include "PE.h"
#include "file_analysis/Manager.h"
using namespace file_analysis;
PE::PE(RecordVal* args, File* file)
: file_analysis::Analyzer(file_mgr->GetComponentTag("PE"), args, file)
{
conn = new binpac::PE::MockConnection(this);
interp = new binpac::PE::File(conn);
done = false;
}
PE::~PE()
{
delete interp;
delete conn;
}
bool PE::DeliverStream(const u_char* data, uint64 len)
{
if ( conn->is_done() )
return true;
try
{
interp->NewData(data, data + len);
}
catch ( const binpac::Exception& e )
{
return false;
}
return true;
}
bool PE::EndOfFile()
{
return false;
}

View file

@ -0,0 +1,35 @@
#ifndef FILE_ANALYSIS_PE_H
#define FILE_ANALYSIS_PE_H
#include <string>
#include "Val.h"
#include "../File.h"
#include "pe_pac.h"
namespace file_analysis {
/**
* Analyze Portable Executable files
*/
class PE : public file_analysis::Analyzer {
public:
~PE();
static file_analysis::Analyzer* Instantiate(RecordVal* args, File* file)
{ return new PE(args, file); }
virtual bool DeliverStream(const u_char* data, uint64 len);
virtual bool EndOfFile();
protected:
PE(RecordVal* args, File* file);
binpac::PE::File* interp;
binpac::PE::MockConnection* conn;
bool done;
};
} // namespace file_analysis
#endif

View file

@ -0,0 +1,24 @@
// See the file in the main distribution directory for copyright.
#include "plugin/Plugin.h"
#include "PE.h"
namespace plugin {
namespace Bro_PE {
class Plugin : public plugin::Plugin {
public:
plugin::Configuration Configure()
{
AddComponent(new ::file_analysis::Component("PE", ::file_analysis::PE::Instantiate));
plugin::Configuration config;
config.name = "Bro::PE";
config.description = "Portable Executable analyzer";
return config;
}
} plugin;
}
}

View file

@ -0,0 +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%);

View file

@ -0,0 +1,216 @@
%extern{
#include "Event.h"
#include "file_analysis/File.h"
#include "events.bif.h"
%}
%header{
VectorVal* process_rvas(const RVAS* rvas, const uint16 size);
%}
%code{
VectorVal* process_rvas(const RVAS* rva_table, const uint16 size)
{
VectorVal* rvas = new VectorVal(internal_type("index_vec")->AsVectorType());
for ( uint16 i=0; i < size; ++i )
rvas->Assign(i, new Val((*rva_table->rvas())[i]->size(), TYPE_COUNT));
return rvas;
}
%}
refine flow File += {
function characteristics_to_bro(c: uint32, len: uint8): TableVal
%{
uint64 mask = (len==16) ? 0xFFFF : 0xFFFFFFFF;
TableVal* char_set = new TableVal(internal_type("count_set")->AsTableType());
for ( uint16 i=0; i < len; ++i )
{
if ( ((c >> i) & 0x1) == 1 )
{
Val *ch = new Val((1<<i)&mask, TYPE_COUNT);
char_set->Assign(ch, 0);
Unref(ch);
}
}
return char_set;
%}
function proc_dos_header(h: DOS_Header): bool
%{
if ( pe_dos_header )
{
RecordVal* dh = new RecordVal(BifType::Record::PE::DOSHeader);
dh->Assign(0, new StringVal(${h.signature}.length(), (const char*) ${h.signature}.data()));
dh->Assign(1, new Val(${h.UsedBytesInTheLastPage}, TYPE_COUNT));
dh->Assign(2, new Val(${h.FileSizeInPages}, TYPE_COUNT));
dh->Assign(3, new Val(${h.NumberOfRelocationItems}, TYPE_COUNT));
dh->Assign(4, new Val(${h.HeaderSizeInParagraphs}, TYPE_COUNT));
dh->Assign(5, new Val(${h.MinimumExtraParagraphs}, TYPE_COUNT));
dh->Assign(6, new Val(${h.MaximumExtraParagraphs}, TYPE_COUNT));
dh->Assign(7, new Val(${h.InitialRelativeSS}, TYPE_COUNT));
dh->Assign(8, new Val(${h.InitialSP}, TYPE_COUNT));
dh->Assign(9, new Val(${h.Checksum}, TYPE_COUNT));
dh->Assign(10, new Val(${h.InitialIP}, TYPE_COUNT));
dh->Assign(11, new Val(${h.InitialRelativeCS}, TYPE_COUNT));
dh->Assign(12, new Val(${h.AddressOfRelocationTable}, TYPE_COUNT));
dh->Assign(13, new Val(${h.OverlayNumber}, TYPE_COUNT));
dh->Assign(14, new Val(${h.OEMid}, TYPE_COUNT));
dh->Assign(15, new Val(${h.OEMinfo}, TYPE_COUNT));
dh->Assign(16, new Val(${h.AddressOfNewExeHeader}, TYPE_COUNT));
BifEvent::generate_pe_dos_header((analyzer::Analyzer *) connection()->bro_analyzer(),
connection()->bro_analyzer()->GetFile()->GetVal()->Ref(),
dh);
}
return true;
%}
function proc_dos_code(code: bytestring): bool
%{
if ( pe_dos_code )
{
BifEvent::generate_pe_dos_code((analyzer::Analyzer *) connection()->bro_analyzer(),
connection()->bro_analyzer()->GetFile()->GetVal()->Ref(),
new StringVal(code.length(), (const char*) code.data()));
}
return true;
%}
function proc_nt_headers(h: NT_Headers): bool
%{
if ( ${h.PESignature} != 17744 ) // Number is uint32 version of "PE\0\0"
{
return false;
// FileViolation("PE Header signature is incorrect.");
}
return true;
%}
function proc_file_header(h: File_Header): bool
%{
if ( pe_file_header )
{
RecordVal* fh = new RecordVal(BifType::Record::PE::FileHeader);
fh->Assign(0, new Val(${h.Machine}, TYPE_COUNT));
fh->Assign(1, new Val(static_cast<double>(${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, 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);
}
return true;
%}
function proc_optional_header(h: Optional_Header): bool
%{
if ( ${h.magic} != 0x10b && // normal pe32 executable
${h.magic} != 0x107 && // rom image
${h.magic} != 0x20b ) // pe32+ executable
{
// FileViolation("PE Optional Header magic is invalid.");
return false;
}
if ( pe_optional_header )
{
RecordVal* oh = new RecordVal(BifType::Record::PE::OptionalHeader);
oh->Assign(0, new Val(${h.magic}, TYPE_COUNT));
oh->Assign(1, new Val(${h.major_linker_version}, TYPE_COUNT));
oh->Assign(2, new Val(${h.minor_linker_version}, TYPE_COUNT));
oh->Assign(3, new Val(${h.size_of_code}, TYPE_COUNT));
oh->Assign(4, new Val(${h.size_of_init_data}, TYPE_COUNT));
oh->Assign(5, new Val(${h.size_of_uninit_data}, TYPE_COUNT));
oh->Assign(6, new Val(${h.addr_of_entry_point}, TYPE_COUNT));
oh->Assign(7, new Val(${h.base_of_code}, TYPE_COUNT));
if ( ${h.pe_format} != PE32_PLUS )
oh->Assign(8, new Val(${h.base_of_data}, TYPE_COUNT));
oh->Assign(9, new Val(${h.image_base}, TYPE_COUNT));
oh->Assign(10, new Val(${h.section_alignment}, TYPE_COUNT));
oh->Assign(11, new Val(${h.file_alignment}, TYPE_COUNT));
oh->Assign(12, new Val(${h.os_version_major}, TYPE_COUNT));
oh->Assign(13, new Val(${h.os_version_minor}, TYPE_COUNT));
oh->Assign(14, new Val(${h.major_image_version}, TYPE_COUNT));
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.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(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(),
oh);
}
return true;
%}
function proc_section_header(h: Section_Header): bool
%{
if ( pe_section_header )
{
RecordVal* section_header = new RecordVal(BifType::Record::PE::SectionHeader);
// Strip null characters from the end of the section name.
u_char* first_null = (u_char*) memchr(${h.name}.data(), 0, ${h.name}.length());
uint16 name_len;
if ( first_null == NULL )
name_len = ${h.name}.length();
else
name_len = first_null - ${h.name}.data();
section_header->Assign(0, new StringVal(name_len, (const char*) ${h.name}.data()));
section_header->Assign(1, new Val(${h.virtual_size}, TYPE_COUNT));
section_header->Assign(2, new Val(${h.virtual_addr}, TYPE_COUNT));
section_header->Assign(3, new Val(${h.size_of_raw_data}, TYPE_COUNT));
section_header->Assign(4, new Val(${h.ptr_to_raw_data}, TYPE_COUNT));
section_header->Assign(5, new Val(${h.non_used_ptr_to_relocs}, TYPE_COUNT));
section_header->Assign(6, new Val(${h.non_used_ptr_to_line_nums}, TYPE_COUNT));
section_header->Assign(7, new Val(${h.non_used_num_of_relocs}, TYPE_COUNT));
section_header->Assign(8, new Val(${h.non_used_num_of_line_nums}, TYPE_COUNT));
section_header->Assign(9, characteristics_to_bro(${h.characteristics}, 32));
BifEvent::generate_pe_section_header((analyzer::Analyzer *) connection()->bro_analyzer(),
connection()->bro_analyzer()->GetFile()->GetVal()->Ref(),
section_header);
}
return true;
%}
};
refine typeattr DOS_Header += &let {
proc : bool = $context.flow.proc_dos_header(this);
};
refine typeattr DOS_Code += &let {
proc : bool = $context.flow.proc_dos_code(code);
};
refine typeattr NT_Headers += &let {
proc : bool = $context.flow.proc_nt_headers(this);
};
refine typeattr File_Header += &let {
proc : bool = $context.flow.proc_file_header(this);
};
refine typeattr Optional_Header += &let {
proc : bool = $context.flow.proc_optional_header(this);
};
refine typeattr Section_Header += &let {
proc: bool = $context.flow.proc_section_header(this);
};

View file

@ -0,0 +1,168 @@
type Headers = record {
dos_header : DOS_Header;
dos_code : DOS_Code(dos_code_len);
pe_header : NT_Headers;
section_headers : Section_Headers(pe_header.file_header.NumberOfSections);
} &let {
dos_code_len: uint32 = dos_header.AddressOfNewExeHeader > 64 ? dos_header.AddressOfNewExeHeader - 64 : 0;
length: uint64 = 64 + dos_code_len + pe_header.length + section_headers.length;
};
# The DOS header gives us the offset of the NT headers
type DOS_Header = record {
signature : bytestring &length=2;
UsedBytesInTheLastPage : uint16;
FileSizeInPages : uint16;
NumberOfRelocationItems : uint16;
HeaderSizeInParagraphs : uint16;
MinimumExtraParagraphs : uint16;
MaximumExtraParagraphs : uint16;
InitialRelativeSS : uint16;
InitialSP : uint16;
Checksum : uint16;
InitialIP : uint16;
InitialRelativeCS : uint16;
AddressOfRelocationTable : uint16;
OverlayNumber : uint16;
Reserved : uint16[4];
OEMid : uint16;
OEMinfo : uint16;
Reserved2 : uint16[10];
AddressOfNewExeHeader : uint32;
} &length=64;
type DOS_Code(len: uint32) = record {
code : bytestring &length=len;
};
# The NT headers give us the file and the optional headers.
type NT_Headers = record {
PESignature : uint32;
file_header : File_Header;
have_opt_header : case is_exe of {
true -> optional_header : Optional_Header &length=file_header.SizeOfOptionalHeader;
false -> none: empty;
};
} &let {
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
type File_Header = record {
Machine : uint16;
NumberOfSections : uint16;
TimeDateStamp : uint32;
PointerToSymbolTable : uint32;
NumberOfSymbols : uint32;
SizeOfOptionalHeader : uint16;
Characteristics : uint16;
};
# The optional header gives us DLL link information, and some structural information
type Optional_Header = record {
magic : uint16;
major_linker_version : uint8;
minor_linker_version : uint8;
size_of_code : uint32;
size_of_init_data : uint32;
size_of_uninit_data : uint32;
addr_of_entry_point : uint32;
base_of_code : uint32;
have_base_of_data: case pe_format of {
PE32 -> base_of_data: uint32;
default -> not_present: empty;
} &requires(pe_format);
is_pe32: case pe_format of {
PE32_PLUS -> image_base_64: uint64;
default -> image_base_32: uint32;
} &requires(pe_format);
section_alignment : uint32;
file_alignment : uint32;
os_version_major : uint16;
os_version_minor : uint16;
major_image_version : uint16;
minor_image_version : uint16;
major_subsys_version : uint16;
minor_subsys_version : uint16;
win32_version : uint32;
size_of_image : uint32;
size_of_headers : uint32;
checksum : uint32;
subsystem : uint16;
dll_characteristics : uint16;
mem: case pe_format of {
PE32 -> i32: Mem_Info32;
PE32_PLUS -> i64: Mem_Info64;
default -> InvalidPEFile : empty;
} &requires(pe_format);
loader_flags : uint32;
number_of_rva_and_sizes : uint32;
rvas : RVAS(number_of_rva_and_sizes);
} &let {
pe_format : uint8 = $context.connection.set_pe32_format(magic);
image_base: uint64 = pe_format == PE32_PLUS ? image_base_64 : image_base_32;
};
type Section_Headers(num: uint16) = record {
sections : Section_Header[num];
} &let {
length: uint32 = num*40;
} &length=length;
type Section_Header = record {
name : bytestring &length=8;
virtual_size : uint32;
virtual_addr : uint32;
size_of_raw_data : uint32;
ptr_to_raw_data : uint32;
non_used_ptr_to_relocs : uint32;
non_used_ptr_to_line_nums : uint32;
non_used_num_of_relocs : uint16;
non_used_num_of_line_nums : uint16;
characteristics : uint32;
} &let {
add_section: bool = $context.connection.add_section(this);
} &length=40;
refine connection MockConnection += {
%member{
uint64 max_file_location_;
uint8 pe32_format_;
%}
%init{
max_file_location_ = 0;
pe32_format_ = UNKNOWN_VERSION;;
%}
function add_section(h: Section_Header): bool
%{
if ( ${h.size_of_raw_data} + ${h.ptr_to_raw_data} > max_file_location_ )
max_file_location_ = ${h.size_of_raw_data} + ${h.ptr_to_raw_data};
return true;
%}
function set_pe32_format(magic: uint16): uint8
%{
if ( ${magic} == 0x10b )
pe32_format_ = PE32;
if ( ${magic} == 0x20b )
pe32_format_ = PE32_PLUS;
return pe32_format_;
%}
function get_max_file_location(): uint64
%{
return max_file_location_;
%}
function get_pe32_format(): uint8
%{
return pe32_format_;
%}
};

View file

@ -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<uint32> 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_;
%}
};

View file

@ -0,0 +1,37 @@
# Basic PE types
enum PE_File_Format {
UNKNOWN_VERSION = 0,
PE32 = 1,
PE32_PLUS = 2,
};
type Mem_Info32 = record {
size_of_stack_reserve : uint32;
size_of_stack_commit : uint32;
size_of_heap_reserve : uint32;
size_of_heap_commit : uint32;
} &byteorder=littleendian &length=16;
type Mem_Info64 = record {
size_of_stack_reserve : uint64;
size_of_stack_commit : uint64;
size_of_heap_reserve : uint64;
size_of_heap_commit : uint64;
} &byteorder=littleendian &length=32;
type RVAS(num: uint32) = record {
rvas : RVA[num];
};
type RVA = record {
virtual_address : uint32;
size : uint32;
} &length=8;
# The BinPAC padding type doesn't work here.
type Padding(length: uint64) = record {
pad: bytestring &length=length &transient;
};
type null_terminated_string = RE/[A-Za-z0-9.]+\x00/;

View file

@ -0,0 +1,40 @@
%include pe-file-types.pac
%include pe-file-headers.pac
# The base record for a Portable Executable file
type PE_File = case $context.connection.is_done() of {
false -> PE : Portable_Executable;
true -> overlay : bytestring &length=1 &transient;
};
type Portable_Executable = record {
headers : Headers;
pad : Padding(restofdata);
} &let {
unparsed_hdr_len: uint32 = headers.pe_header.size_of_headers - headers.length;
data_post_hdrs: uint64 = $context.connection.get_max_file_location() - headers.pe_header.size_of_headers + unparsed_hdr_len;
restofdata: uint64 = headers.pe_header.is_exe ? data_post_hdrs : 0;
proc: bool = $context.connection.mark_done();
} &byteorder=littleendian;
refine connection MockConnection += {
%member{
bool done_;
%}
%init{
done_ = false;
%}
function mark_done(): bool
%{
done_ = true;
return true;
%}
function is_done(): bool
%{
return done_;
%}
};

View file

@ -0,0 +1,20 @@
%include binpac.pac
%include bro.pac
analyzer PE withcontext {
connection: MockConnection;
flow: File;
};
connection MockConnection(bro_analyzer: BroFileAnalyzer) {
upflow = File;
downflow = File;
};
%include pe-file.pac
flow File {
flowunit = PE_File withcontext(connection, this);
}
%include pe-analyzer.pac