mirror of
https://github.com/zeek/zeek.git
synced 2025-10-04 07:38:19 +00:00
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:
commit
89b4d79f93
1081 changed files with 38403 additions and 11012 deletions
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
#include "Val.h"
|
||||
|
||||
#include "../config.h"
|
||||
#include "../bro-config.h"
|
||||
#include "../util.h"
|
||||
|
||||
namespace file_analysis {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
10
src/file_analysis/analyzer/pe/CMakeLists.txt
Normal file
10
src/file_analysis/analyzer/pe/CMakeLists.txt
Normal 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()
|
39
src/file_analysis/analyzer/pe/PE.cc
Normal file
39
src/file_analysis/analyzer/pe/PE.cc
Normal 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;
|
||||
}
|
35
src/file_analysis/analyzer/pe/PE.h
Normal file
35
src/file_analysis/analyzer/pe/PE.h
Normal 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
|
24
src/file_analysis/analyzer/pe/Plugin.cc
Normal file
24
src/file_analysis/analyzer/pe/Plugin.cc
Normal 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;
|
||||
|
||||
}
|
||||
}
|
57
src/file_analysis/analyzer/pe/events.bif
Normal file
57
src/file_analysis/analyzer/pe/events.bif
Normal 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%);
|
216
src/file_analysis/analyzer/pe/pe-analyzer.pac
Normal file
216
src/file_analysis/analyzer/pe/pe-analyzer.pac
Normal 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);
|
||||
};
|
168
src/file_analysis/analyzer/pe/pe-file-headers.pac
Normal file
168
src/file_analysis/analyzer/pe/pe-file-headers.pac
Normal 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_;
|
||||
%}
|
||||
};
|
183
src/file_analysis/analyzer/pe/pe-file-idata.pac
Normal file
183
src/file_analysis/analyzer/pe/pe-file-idata.pac
Normal 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_;
|
||||
%}
|
||||
|
||||
};
|
37
src/file_analysis/analyzer/pe/pe-file-types.pac
Normal file
37
src/file_analysis/analyzer/pe/pe-file-types.pac
Normal 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/;
|
40
src/file_analysis/analyzer/pe/pe-file.pac
Normal file
40
src/file_analysis/analyzer/pe/pe-file.pac
Normal 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_;
|
||||
%}
|
||||
};
|
20
src/file_analysis/analyzer/pe/pe.pac
Normal file
20
src/file_analysis/analyzer/pe/pe.pac
Normal 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
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue