Merge remote-tracking branch 'origin/master' into topic/seth/file-entropy

# Conflicts:
#	scripts/test-all-policy.bro
#	testing/btest/Baseline/coverage.default-load-baseline/canonified_loaded_scripts.log
This commit is contained in:
Seth Hall 2016-03-21 11:39:15 -04:00
commit 89b4d79f93
1081 changed files with 38403 additions and 11012 deletions

View file

@ -52,9 +52,10 @@ bool AnalyzerSet::Add(file_analysis::Tag tag, RecordVal* args)
if ( analyzer_map.Lookup(key) )
{
DBG_LOG(DBG_FILE_ANALYSIS, "Instantiate analyzer %s skipped for file id"
" %s: already exists", file_mgr->GetComponentName(tag).c_str(),
file->GetID().c_str());
DBG_LOG(DBG_FILE_ANALYSIS, "[%s] Instantiate analyzer %s skipped: already exists",
file->GetID().c_str(),
file_mgr->GetComponentName(tag).c_str());
delete key;
return true;
}
@ -92,9 +93,9 @@ bool AnalyzerSet::AddMod::Perform(AnalyzerSet* set)
{
if ( set->analyzer_map.Lookup(key) )
{
DBG_LOG(DBG_FILE_ANALYSIS, "Add analyzer %s skipped for file id"
" %s: already exists", file_mgr->GetComponentName(a->Tag()).c_str(),
a->GetFile()->GetID().c_str());
DBG_LOG(DBG_FILE_ANALYSIS, "[%s] Add analyzer %s skipped: already exists",
a->GetFile()->GetID().c_str(),
file_mgr->GetComponentName(a->Tag()).c_str());
Abort();
return true;
@ -119,14 +120,14 @@ bool AnalyzerSet::Remove(file_analysis::Tag tag, HashKey* key)
if ( ! a )
{
DBG_LOG(DBG_FILE_ANALYSIS, "Skip remove analyzer %s for file id %s",
file_mgr->GetComponentName(tag).c_str(), file->GetID().c_str());
DBG_LOG(DBG_FILE_ANALYSIS, "[%s] Skip remove analyzer %s",
file->GetID().c_str(), file_mgr->GetComponentName(tag).c_str());
return false;
}
DBG_LOG(DBG_FILE_ANALYSIS, "Remove analyzer %s for file id %s",
file_mgr->GetComponentName(tag).c_str(),
file->GetID().c_str());
DBG_LOG(DBG_FILE_ANALYSIS, "[%s] Remove analyzer %s",
file->GetID().c_str(),
file_mgr->GetComponentName(tag).c_str());
a->Done();
delete a;
@ -168,8 +169,9 @@ file_analysis::Analyzer* AnalyzerSet::InstantiateAnalyzer(Tag tag,
if ( ! a )
{
reporter->Error("Failed file analyzer %s instantiation for file id %s",
file_mgr->GetComponentName(tag).c_str(), file->GetID().c_str());
reporter->Error("[%s] Failed file analyzer %s instantiation",
file->GetID().c_str(),
file_mgr->GetComponentName(tag).c_str());
return 0;
}
@ -178,8 +180,8 @@ file_analysis::Analyzer* AnalyzerSet::InstantiateAnalyzer(Tag tag,
void AnalyzerSet::Insert(file_analysis::Analyzer* a, HashKey* key)
{
DBG_LOG(DBG_FILE_ANALYSIS, "Add analyzer %s for file id %s",
file_mgr->GetComponentName(a->Tag()).c_str(), file->GetID().c_str());
DBG_LOG(DBG_FILE_ANALYSIS, "[%s] Add analyzer %s",
file->GetID().c_str(), file_mgr->GetComponentName(a->Tag()).c_str());
analyzer_map.Insert(key, a);
delete key;
@ -191,7 +193,7 @@ void AnalyzerSet::DrainModifications()
if ( mod_queue.empty() )
return;
DBG_LOG(DBG_FILE_ANALYSIS, "Start analyzer mod queue flush of file id %s",
DBG_LOG(DBG_FILE_ANALYSIS, "[%s] Start analyzer mod queue flush",
file->GetID().c_str());
do
{
@ -200,6 +202,6 @@ void AnalyzerSet::DrainModifications()
delete mod;
mod_queue.pop();
} while ( ! mod_queue.empty() );
DBG_LOG(DBG_FILE_ANALYSIS, "End flushing analyzer mod queue of file id %s",
DBG_LOG(DBG_FILE_ANALYSIS, "[%s] End flushing analyzer mod queue.",
file->GetID().c_str());
}

View file

@ -9,7 +9,7 @@
#include "Val.h"
#include "../config.h"
#include "../bro-config.h"
#include "../util.h"
namespace file_analysis {

View file

@ -53,32 +53,36 @@ int File::overflow_bytes_idx = -1;
int File::timeout_interval_idx = -1;
int File::bof_buffer_size_idx = -1;
int File::bof_buffer_idx = -1;
int File::meta_mime_type_idx = -1;
int File::meta_mime_types_idx = -1;
void File::StaticInit()
{
if ( id_idx != -1 )
return;
id_idx = Idx("id");
parent_id_idx = Idx("parent_id");
source_idx = Idx("source");
is_orig_idx = Idx("is_orig");
conns_idx = Idx("conns");
last_active_idx = Idx("last_active");
seen_bytes_idx = Idx("seen_bytes");
total_bytes_idx = Idx("total_bytes");
missing_bytes_idx = Idx("missing_bytes");
overflow_bytes_idx = Idx("overflow_bytes");
timeout_interval_idx = Idx("timeout_interval");
bof_buffer_size_idx = Idx("bof_buffer_size");
bof_buffer_idx = Idx("bof_buffer");
id_idx = Idx("id", fa_file_type);
parent_id_idx = Idx("parent_id", fa_file_type);
source_idx = Idx("source", fa_file_type);
is_orig_idx = Idx("is_orig", fa_file_type);
conns_idx = Idx("conns", fa_file_type);
last_active_idx = Idx("last_active", fa_file_type);
seen_bytes_idx = Idx("seen_bytes", fa_file_type);
total_bytes_idx = Idx("total_bytes", fa_file_type);
missing_bytes_idx = Idx("missing_bytes", fa_file_type);
overflow_bytes_idx = Idx("overflow_bytes", fa_file_type);
timeout_interval_idx = Idx("timeout_interval", fa_file_type);
bof_buffer_size_idx = Idx("bof_buffer_size", fa_file_type);
bof_buffer_idx = Idx("bof_buffer", fa_file_type);
meta_mime_type_idx = Idx("mime_type", fa_metadata_type);
meta_mime_types_idx = Idx("mime_types", fa_metadata_type);
}
File::File(const string& file_id, const string& source_name, Connection* conn,
analyzer::Tag tag, bool is_orig)
: id(file_id), val(0), file_reassembler(0), stream_offset(0),
reassembly_max_buffer(0), did_mime_type(false),
reassembly_enabled(false), postpone_timeout(false), done(false),
: id(file_id), val(0), file_reassembler(0), stream_offset(0),
reassembly_max_buffer(0), did_metadata_inference(false),
reassembly_enabled(false), postpone_timeout(false), done(false),
analyzers(this)
{
StaticInit();
@ -169,11 +173,13 @@ double File::LookupFieldDefaultInterval(int idx) const
return rval;
}
int File::Idx(const string& field)
int File::Idx(const string& field, const RecordType* type)
{
int rval = fa_file_type->FieldOffset(field.c_str());
int rval = type->FieldOffset(field.c_str());
if ( rval < 0 )
reporter->InternalError("Unknown fa_file field: %s", field.c_str());
reporter->InternalError("Unknown %s field: %s", type->GetName().c_str(),
field.c_str());
return rval;
}
@ -281,48 +287,46 @@ void File::SetReassemblyBuffer(uint64 max)
reassembly_max_buffer = max;
}
bool File::DetectMIME()
void File::InferMetadata()
{
did_mime_type = true;
did_metadata_inference = true;
Val* bof_buffer_val = val->Lookup(bof_buffer_idx);
if ( ! bof_buffer_val )
{
if ( bof_buffer.size == 0 )
return false;
return;
BroString* bs = concatenate(bof_buffer.chunks);
bof_buffer_val = new StringVal(bs);
val->Assign(bof_buffer_idx, bof_buffer_val);
}
if ( ! FileEventAvailable(file_sniff) )
return;
RuleMatcher::MIME_Matches matches;
const u_char* data = bof_buffer_val->AsString()->Bytes();
uint64 len = bof_buffer_val->AsString()->Len();
len = min(len, LookupFieldDefaultCount(bof_buffer_size_idx));
file_mgr->DetectMIME(data, len, &matches);
if ( matches.empty() )
return false;
val_list* vl = new val_list();
vl->append(val->Ref());
RecordVal* meta = new RecordVal(fa_metadata_type);
vl->append(meta);
if ( FileEventAvailable(file_mime_type) )
if ( ! matches.empty() )
{
val_list* vl = new val_list();
vl->append(val->Ref());
vl->append(new StringVal(*(matches.begin()->second.begin())));
FileEvent(file_mime_type, vl);
meta->Assign(meta_mime_type_idx,
new StringVal(*(matches.begin()->second.begin())));
meta->Assign(meta_mime_types_idx,
file_analysis::GenMIMEMatchesVal(matches));
}
if ( FileEventAvailable(file_mime_types) )
{
val_list* vl = new val_list();
vl->append(val->Ref());
vl->append(file_analysis::GenMIMEMatchesVal(matches));
FileEvent(file_mime_types, vl);
}
return true;
FileEvent(file_sniff, vl);
return;
}
bool File::BufferBOF(const u_char* data, uint64 len)
@ -355,9 +359,9 @@ void File::DeliverStream(const u_char* data, uint64 len)
// Buffer enough data for the BOF buffer
BufferBOF(data, len);
if ( ! did_mime_type && bof_buffer.full &&
if ( ! did_metadata_inference && bof_buffer.full &&
LookupFieldDefaultCount(missing_bytes_idx) == 0 )
DetectMIME();
InferMetadata();
DBG_LOG(DBG_FILE_ANALYSIS,
"[%s] %" PRIu64 " stream bytes in at offset %" PRIu64 "; %s [%s%s]",
@ -438,7 +442,7 @@ void File::DeliverChunk(const u_char* data, uint64 len, uint64 offset)
}
else if ( reassembly_enabled )
{
// This is data that doesn't match the offset and the reassembler
// This is data that doesn't match the offset and the reassembler
// needs to be enabled.
file_reassembler = new FileReassembler(this, stream_offset);
file_reassembler->NewBlock(network_time, offset, len, data);
@ -502,10 +506,10 @@ void File::EndOfFile()
// any stream analyzers.
if ( ! bof_buffer.full )
{
DBG_LOG(DBG_FILE_ANALYSIS, "[%s] File over but bof_buffer not full.", id.c_str());
bof_buffer.full = true;
DeliverStream((const u_char*) "", 0);
}
analyzers.DrainModifications();
done = true;
@ -536,7 +540,12 @@ void File::Gap(uint64 offset, uint64 len)
return;
}
analyzers.DrainModifications();
if ( ! bof_buffer.full )
{
DBG_LOG(DBG_FILE_ANALYSIS, "[%s] File gap before bof_buffer filled, continued without attempting to fill bof_buffer.", id.c_str());
bof_buffer.full = true;
DeliverStream((const u_char*) "", 0);
}
file_analysis::Analyzer* a = 0;
IterCookie* c = analyzers.InitForIteration();
@ -582,7 +591,7 @@ void File::FileEvent(EventHandlerPtr h, val_list* vl)
mgr.QueueEvent(h, vl);
if ( h == file_new || h == file_over_new_connection ||
h == file_mime_type ||
h == file_sniff ||
h == file_timeout || h == file_extraction_limit )
{
// immediate feedback is required for these events.

View file

@ -230,12 +230,11 @@ protected:
bool BufferBOF(const u_char* data, uint64 len);
/**
* Does mime type detection via file magic signatures and assigns
* strongest matching mime type (if available) to \c mime_type
* field in #val. It uses the data in the BOF buffer.
* @return whether a mime type match was found.
* Does metadata inference (e.g. mime type detection via file
* magic signatures) using data in the BOF (beginning-of-file) buffer
* and raises an event with the metadata.
*/
bool DetectMIME();
void InferMetadata();
/**
* Enables reassembly on the file.
@ -266,10 +265,11 @@ protected:
/**
* Lookup a record field index/offset by name.
* @param field_name the name of the \c fa_file record field.
* @param field_name the name of the record field.
* @param type the record type for which the field will be looked up.
* @return the field offset in #val record corresponding to \a field_name.
*/
static int Idx(const string& field_name);
static int Idx(const string& field_name, const RecordType* type);
/**
* Initializes static member.
@ -282,7 +282,7 @@ protected:
FileReassembler* file_reassembler; /**< A reassembler for the file if it's needed. */
uint64 stream_offset; /**< The offset of the file which has been forwarded. */
uint64 reassembly_max_buffer; /**< Maximum allowed buffer for reassembly. */
bool did_mime_type; /**< Whether the mime type ident has already been attempted. */
bool did_metadata_inference; /**< Whether the metadata inference has already been attempted. */
bool reassembly_enabled; /**< Whether file stream reassembly is needed. */
bool postpone_timeout; /**< Whether postponing timeout is requested. */
bool done; /**< If this object is about to be deleted. */
@ -313,6 +313,9 @@ protected:
static int bof_buffer_idx;
static int mime_type_idx;
static int mime_types_idx;
static int meta_mime_type_idx;
static int meta_mime_types_idx;
};
} // namespace file_analysis

View file

@ -52,9 +52,9 @@ protected:
DECLARE_SERIAL(FileReassembler);
void Undelivered(uint64 up_to_seq);
void BlockInserted(DataBlock* b);
void Overlap(const u_char* b1, const u_char* b2, uint64 n);
void Undelivered(uint64 up_to_seq) override;
void BlockInserted(DataBlock* b) override;
void Overlap(const u_char* b1, const u_char* b2, uint64 n) override;
File* the_file;
bool flushing;

View file

@ -390,7 +390,7 @@ bool Manager::RemoveFile(const string& file_id)
if ( ! f )
return false;
DBG_LOG(DBG_FILE_ANALYSIS, "Remove FileID %s", file_id.c_str());
DBG_LOG(DBG_FILE_ANALYSIS, "[%s] Remove file", file_id.c_str());
f->EndOfFile();
delete f;
@ -467,8 +467,8 @@ Analyzer* Manager::InstantiateAnalyzer(Tag tag, RecordVal* args, File* f) const
return 0;
}
DBG_LOG(DBG_FILE_ANALYSIS, "Instantiate analyzer %s for file %s",
GetComponentName(tag).c_str(), f->id.c_str());
DBG_LOG(DBG_FILE_ANALYSIS, "[%s] Instantiate analyzer %s",
f->id.c_str(), GetComponentName(tag).c_str());
Analyzer* a = c->Factory()(args, f);

View file

@ -3,7 +3,7 @@
#ifndef FILE_ANALYZER_TAG_H
#define FILE_ANALYZER_TAG_H
#include "config.h"
#include "bro-config.h"
#include "util.h"
#include "../Tag.h"
#include "plugin/TaggedComponent.h"

View file

@ -2,5 +2,6 @@ add_subdirectory(data_event)
add_subdirectory(entropy)
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);
%}
%code{
VectorVal* process_rvas(const RVAS* rva_table)
{
VectorVal* rvas = new VectorVal(internal_type("index_vec")->AsVectorType());
for ( uint16 i=0; i < rva_table->rvas()->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}));
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

View file

@ -52,7 +52,8 @@ bool file_analysis::X509::EndOfFile()
X509Val* cert_val = new X509Val(ssl_cert); // cert_val takes ownership of ssl_cert
RecordVal* cert_record = ParseCertificate(cert_val); // parse basic information into record
// parse basic information into record.
RecordVal* cert_record = ParseCertificate(cert_val, GetFile()->GetID().c_str());
// and send the record on to scriptland
val_list* vl = new val_list();
@ -84,7 +85,7 @@ bool file_analysis::X509::EndOfFile()
return false;
}
RecordVal* file_analysis::X509::ParseCertificate(X509Val* cert_val)
RecordVal* file_analysis::X509::ParseCertificate(X509Val* cert_val, const char* fid)
{
::X509* ssl_cert = cert_val->GetCertificate();
@ -104,13 +105,35 @@ RecordVal* file_analysis::X509::ParseCertificate(X509Val* cert_val)
len = BIO_gets(bio, buf, sizeof(buf));
pX509Cert->Assign(2, new StringVal(len, buf));
BIO_reset(bio);
X509_NAME *subject_name = X509_get_subject_name(ssl_cert);
// extract the most specific (last) common name from the subject
int namepos = -1;
for ( ;; )
{
int j = X509_NAME_get_index_by_NID(subject_name, NID_commonName, namepos);
if ( j == -1 )
break;
namepos = j;
}
if ( namepos != -1 )
{
// we found a common name
ASN1_STRING_print(bio, X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name, namepos)));
len = BIO_gets(bio, buf, sizeof(buf));
pX509Cert->Assign(4, new StringVal(len, buf));
BIO_reset(bio);
}
X509_NAME_print_ex(bio, X509_get_issuer_name(ssl_cert), 0, XN_FLAG_RFC2253);
len = BIO_gets(bio, buf, sizeof(buf));
pX509Cert->Assign(3, new StringVal(len, buf));
BIO_free(bio);
pX509Cert->Assign(4, new Val(GetTimeFromAsn1(X509_get_notBefore(ssl_cert)), TYPE_TIME));
pX509Cert->Assign(5, new Val(GetTimeFromAsn1(X509_get_notAfter(ssl_cert)), TYPE_TIME));
pX509Cert->Assign(5, new Val(GetTimeFromAsn1(X509_get_notBefore(ssl_cert), fid), TYPE_TIME));
pX509Cert->Assign(6, new Val(GetTimeFromAsn1(X509_get_notAfter(ssl_cert), fid), TYPE_TIME));
// we only read 255 bytes because byte 256 is always 0.
// if the string is longer than 255, that will be our null-termination,
@ -118,28 +141,41 @@ RecordVal* file_analysis::X509::ParseCertificate(X509Val* cert_val)
if ( ! i2t_ASN1_OBJECT(buf, 255, ssl_cert->cert_info->key->algor->algorithm) )
buf[0] = 0;
pX509Cert->Assign(6, new StringVal(buf));
pX509Cert->Assign(7, new StringVal(buf));
// Special case for RDP server certificates. For some reason some (all?) RDP server
// certificates like to specify their key algorithm as md5WithRSAEncryption, which
// is wrong on so many levels. We catch this special case here and set it to what is
// actually should be (namely - rsaEncryption), so that OpenSSL will parse out the
// key later. Otherwise it will just fail to parse the certificate key.
ASN1_OBJECT* old_algorithm = 0;
if ( OBJ_obj2nid(ssl_cert->cert_info->key->algor->algorithm) == NID_md5WithRSAEncryption )
{
old_algorithm = ssl_cert->cert_info->key->algor->algorithm;
ssl_cert->cert_info->key->algor->algorithm = OBJ_nid2obj(NID_rsaEncryption);
}
if ( ! i2t_ASN1_OBJECT(buf, 255, ssl_cert->sig_alg->algorithm) )
buf[0] = 0;
pX509Cert->Assign(7, new StringVal(buf));
pX509Cert->Assign(8, new StringVal(buf));
// Things we can do when we have the key...
EVP_PKEY *pkey = X509_extract_key(ssl_cert);
if ( pkey != NULL )
{
if ( pkey->type == EVP_PKEY_DSA )
pX509Cert->Assign(8, new StringVal("dsa"));
pX509Cert->Assign(9, new StringVal("dsa"));
else if ( pkey->type == EVP_PKEY_RSA )
{
pX509Cert->Assign(8, new StringVal("rsa"));
pX509Cert->Assign(9, new StringVal("rsa"));
char *exponent = BN_bn2dec(pkey->pkey.rsa->e);
if ( exponent != NULL )
{
pX509Cert->Assign(10, new StringVal(exponent));
pX509Cert->Assign(11, new StringVal(exponent));
OPENSSL_free(exponent);
exponent = NULL;
}
@ -147,14 +183,19 @@ RecordVal* file_analysis::X509::ParseCertificate(X509Val* cert_val)
#ifndef OPENSSL_NO_EC
else if ( pkey->type == EVP_PKEY_EC )
{
pX509Cert->Assign(8, new StringVal("ecdsa"));
pX509Cert->Assign(11, KeyCurve(pkey));
pX509Cert->Assign(9, new StringVal("ecdsa"));
pX509Cert->Assign(12, KeyCurve(pkey));
}
#endif
// set key algorithm back. We do not have to free the value that we created because (I think) it
// comes out of a static array from OpenSSL memory.
if ( old_algorithm )
ssl_cert->cert_info->key->algor->algorithm = old_algorithm;
unsigned int length = KeyLength(pkey);
if ( length > 0 )
pX509Cert->Assign(9, new Val(length, TYPE_COUNT));
pX509Cert->Assign(10, new Val(length, TYPE_COUNT));
EVP_PKEY_free(pkey);
}
@ -475,54 +516,103 @@ unsigned int file_analysis::X509::KeyLength(EVP_PKEY *key)
reporter->InternalError("cannot be reached");
}
double file_analysis::X509::GetTimeFromAsn1(const ASN1_TIME* atime)
double file_analysis::X509::GetTimeFromAsn1(const ASN1_TIME* atime, const char* arg_fid)
{
const char *fid = arg_fid ? arg_fid : "";
time_t lResult = 0;
char lBuffer[24];
char lBuffer[26];
char* pBuffer = lBuffer;
size_t lTimeLength = atime->length;
char * pString = (char *) atime->data;
const char *pString = (const char *) atime->data;
unsigned int remaining = atime->length;
if ( atime->type == V_ASN1_UTCTIME )
{
if ( lTimeLength < 11 || lTimeLength > 17 )
if ( remaining < 11 || remaining > 17 )
{
reporter->Weird(fmt("Could not parse time in X509 certificate (fuid %s) -- UTCTime has wrong length", fid));
return 0;
}
if ( pString[remaining-1] != 'Z' )
{
// not valid according to RFC 2459 4.1.2.5.1
reporter->Weird(fmt("Could not parse UTC time in non-YY-format in X509 certificate (x509 %s)", fid));
return 0;
}
// year is first two digits in YY format. Buffer expects YYYY format.
if ( pString[0] - '0' < 50 ) // RFC 2459 4.1.2.5.1
{
*(pBuffer++) = '2';
*(pBuffer++) = '0';
}
else
{
*(pBuffer++) = '1';
*(pBuffer++) = '9';
}
memcpy(pBuffer, pString, 10);
pBuffer += 10;
pString += 10;
remaining -= 10;
}
else
else if ( atime->type == V_ASN1_GENERALIZEDTIME )
{
if ( lTimeLength < 13 )
// generalized time. We apparently ignore the YYYYMMDDHH case
// for now and assume we always have minutes and seconds.
// This should be ok because it is specified as a requirement in RFC 2459 4.1.2.5.2
if ( remaining < 12 || remaining > 23 )
{
reporter->Weird(fmt("Could not parse time in X509 certificate (fuid %s) -- Generalized time has wrong length", fid));
return 0;
}
memcpy(pBuffer, pString, 12);
pBuffer += 12;
pString += 12;
remaining -= 12;
}
else
{
reporter->Weird(fmt("Invalid time type in X509 certificate (fuid %s)", fid));
return 0;
}
if ((*pString == 'Z') || (*pString == '-') || (*pString == '+'))
if ( (remaining == 0) || (*pString == 'Z') || (*pString == '-') || (*pString == '+') )
{
*(pBuffer++) = '0';
*(pBuffer++) = '0';
}
else if ( remaining >= 2 )
{
*(pBuffer++) = *(pString++);
*(pBuffer++) = *(pString++);
remaining -= 2;
// Skip any fractional seconds...
if ( (remaining > 0) && (*pString == '.') )
{
pString++;
remaining--;
while ( (remaining > 0) && (*pString >= '0') && (*pString <= '9') )
{
pString++;
remaining--;
}
}
}
else
{
*(pBuffer++) = *(pString++);
*(pBuffer++) = *(pString++);
// Skip any fractional seconds...
if (*pString == '.')
{
pString++;
while ((*pString >= '0') && (*pString <= '9'))
pString++;
}
reporter->Weird(fmt("Could not parse time in X509 certificate (fuid %s) -- additional char after time", fid));
return 0;
}
*(pBuffer++) = 'Z';
@ -530,31 +620,39 @@ double file_analysis::X509::GetTimeFromAsn1(const ASN1_TIME* atime)
time_t lSecondsFromUTC;
if ( *pString == 'Z' )
if ( remaining == 0 || *pString == 'Z' )
lSecondsFromUTC = 0;
else
{
if ((*pString != '+') && (pString[5] != '-'))
if ( remaining < 5 )
{
reporter->Weird(fmt("Could not parse time in X509 certificate (fuid %s) -- not enough bytes remaining for offset", fid));
return 0;
}
lSecondsFromUTC = ((pString[1]-'0') * 10 + (pString[2]-'0')) * 60;
lSecondsFromUTC += (pString[3]-'0') * 10 + (pString[4]-'0');
if ((*pString != '+') && (*pString != '-'))
{
reporter->Weird(fmt("Could not parse time in X509 certificate (fuid %s) -- unknown offset type", fid));
return 0;
}
lSecondsFromUTC = ((pString[1] - '0') * 10 + (pString[2] - '0')) * 60;
lSecondsFromUTC += (pString[3] - '0') * 10 + (pString[4] - '0');
if (*pString == '-')
lSecondsFromUTC = -lSecondsFromUTC;
}
tm lTime;
lTime.tm_sec = ((lBuffer[10] - '0') * 10) + (lBuffer[11] - '0');
lTime.tm_min = ((lBuffer[8] - '0') * 10) + (lBuffer[9] - '0');
lTime.tm_hour = ((lBuffer[6] - '0') * 10) + (lBuffer[7] - '0');
lTime.tm_mday = ((lBuffer[4] - '0') * 10) + (lBuffer[5] - '0');
lTime.tm_mon = (((lBuffer[2] - '0') * 10) + (lBuffer[3] - '0')) - 1;
lTime.tm_year = ((lBuffer[0] - '0') * 10) + (lBuffer[1] - '0');
lTime.tm_sec = ((lBuffer[12] - '0') * 10) + (lBuffer[13] - '0');
lTime.tm_min = ((lBuffer[10] - '0') * 10) + (lBuffer[11] - '0');
lTime.tm_hour = ((lBuffer[8] - '0') * 10) + (lBuffer[9] - '0');
lTime.tm_mday = ((lBuffer[6] - '0') * 10) + (lBuffer[7] - '0');
lTime.tm_mon = (((lBuffer[4] - '0') * 10) + (lBuffer[5] - '0')) - 1;
lTime.tm_year = (lBuffer[0] - '0') * 1000 + (lBuffer[1] - '0') * 100 + ((lBuffer[2] - '0') * 10) + (lBuffer[3] - '0');
if ( lTime.tm_year < 50 )
lTime.tm_year += 100; // RFC 2459
if ( lTime.tm_year > 1900)
lTime.tm_year -= 1900;
lTime.tm_wday = 0;
lTime.tm_yday = 0;
@ -564,7 +662,7 @@ double file_analysis::X509::GetTimeFromAsn1(const ASN1_TIME* atime)
if ( lResult )
{
if ( 0 != lTime.tm_isdst )
if ( lTime.tm_isdst != 0 )
lResult -= 3600; // mktime may adjust for DST (OS dependent)
lResult += lSecondsFromUTC;

View file

@ -29,10 +29,13 @@ public:
*
* @param cert_val The certificate to converts.
*
* @param fid A file ID associated with the certificate, if any
* (primarily for error reporting).
*
* @param Returns the new record value and passes ownership to
* caller.
*/
static RecordVal* ParseCertificate(X509Val* cert_val);
static RecordVal* ParseCertificate(X509Val* cert_val, const char* fid = 0);
static file_analysis::Analyzer* Instantiate(RecordVal* args, File* file)
{ return new X509(args, file); }
@ -59,7 +62,7 @@ private:
std::string cert_data;
// Helpers for ParseCertificate.
static double GetTimeFromAsn1(const ASN1_TIME * atime);
static double GetTimeFromAsn1(const ASN1_TIME * atime, const char* fid);
static StringVal* KeyCurve(EVP_PKEY *key);
static unsigned int KeyLength(EVP_PKEY *key);
};