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

Conflicts:
	scripts/base/init-default.bro
	src/file_analysis/analyzer/CMakeLists.txt
This commit is contained in:
Vlad Grigorescu 2014-06-21 13:15:14 -04:00
commit b91b0646b8
1719 changed files with 78600 additions and 198617 deletions

View file

@ -0,0 +1,11 @@
// See the file "COPYING" in the main distribution directory for copyright.
#include "Analyzer.h"
#include "Manager.h"
file_analysis::Analyzer::~Analyzer()
{
DBG_LOG(DBG_FILE_ANALYSIS, "Destroy file analyzer %s",
file_mgr->GetComponentName(tag));
Unref(args);
}

View file

@ -5,14 +5,12 @@
#include "Val.h"
#include "NetVar.h"
#include "analyzer/Tag.h"
#include "Tag.h"
#include "file_analysis/file_analysis.bif.h"
namespace file_analysis {
typedef int FA_Tag;
class File;
/**
@ -25,11 +23,7 @@ public:
* Destructor. Nothing special about it. Virtual since we definitely expect
* to delete instances of derived classes via pointers to this class.
*/
virtual ~Analyzer()
{
DBG_LOG(DBG_FILE_ANALYSIS, "Destroy file analyzer %d", tag);
Unref(args);
}
virtual ~Analyzer();
/**
* Subclasses may override this metod to receive file data non-sequentially.
@ -76,7 +70,7 @@ public:
/**
* @return the analyzer type enum value.
*/
FA_Tag Tag() const { return tag; }
file_analysis::Tag Tag() const { return tag; }
/**
* @return the AnalyzerArgs associated with the analyzer.
@ -88,18 +82,6 @@ public:
*/
File* GetFile() const { return file; }
/**
* Retrieves an analyzer tag field from full analyzer argument record.
* @param args an \c AnalyzerArgs (script-layer type) value.
* @return the analyzer tag equivalent of the 'tag' field from the
* \c AnalyzerArgs value \a args.
*/
static FA_Tag ArgsTag(const RecordVal* args)
{
using BifType::Record::FileAnalysis::AnalyzerArgs;
return args->Lookup(AnalyzerArgs->FieldOffset("tag"))->AsEnum();
}
protected:
/**
@ -108,15 +90,15 @@ protected:
* tunable options, if any, related to a particular analyzer type.
* @param arg_file the file to which the the analyzer is being attached.
*/
Analyzer(RecordVal* arg_args, File* arg_file)
: tag(file_analysis::Analyzer::ArgsTag(arg_args)),
Analyzer(file_analysis::Tag arg_tag, RecordVal* arg_args, File* arg_file)
: tag(arg_tag),
args(arg_args->Ref()->AsRecordVal()),
file(arg_file)
{}
private:
FA_Tag tag; /**< The particular analyzer type of the analyzer instance. */
file_analysis::Tag tag; /**< The particular type of the analyzer instance. */
RecordVal* args; /**< \c AnalyzerArgs val gives tunable analyzer params. */
File* file; /**< The file to which the analyzer is attached. */
};

View file

@ -15,7 +15,8 @@ static void analyzer_del_func(void* v)
AnalyzerSet::AnalyzerSet(File* arg_file) : file(arg_file)
{
TypeList* t = new TypeList();
t->Append(BifType::Record::FileAnalysis::AnalyzerArgs->Ref());
t->Append(file_mgr->GetTagEnumType()->Ref());
t->Append(BifType::Record::Files::AnalyzerArgs->Ref());
analyzer_hash = new CompositeHash(t);
Unref(t);
analyzer_map.SetDeleteFunc(analyzer_del_func);
@ -34,20 +35,28 @@ AnalyzerSet::~AnalyzerSet()
delete analyzer_hash;
}
bool AnalyzerSet::Add(RecordVal* args)
Analyzer* AnalyzerSet::Find(file_analysis::Tag tag, RecordVal* args)
{
HashKey* key = GetKey(args);
HashKey* key = GetKey(tag, args);
Analyzer* rval = analyzer_map.Lookup(key);
delete key;
return rval;
}
bool AnalyzerSet::Add(file_analysis::Tag tag, RecordVal* args)
{
HashKey* key = GetKey(tag, args);
if ( analyzer_map.Lookup(key) )
{
DBG_LOG(DBG_FILE_ANALYSIS, "Instantiate analyzer %d skipped for file id"
" %s: already exists", file_analysis::Analyzer::ArgsTag(args),
DBG_LOG(DBG_FILE_ANALYSIS, "Instantiate analyzer %s skipped for file id"
" %s: already exists", file_mgr->GetComponentName(tag),
file->GetID().c_str());
delete key;
return true;
}
file_analysis::Analyzer* a = InstantiateAnalyzer(args);
file_analysis::Analyzer* a = InstantiateAnalyzer(tag, args);
if ( ! a )
{
@ -60,10 +69,10 @@ bool AnalyzerSet::Add(RecordVal* args)
return true;
}
bool AnalyzerSet::QueueAdd(RecordVal* args)
bool AnalyzerSet::QueueAdd(file_analysis::Tag tag, RecordVal* args)
{
HashKey* key = GetKey(args);
file_analysis::Analyzer* a = InstantiateAnalyzer(args);
HashKey* key = GetKey(tag, args);
file_analysis::Analyzer* a = InstantiateAnalyzer(tag, args);
if ( ! a )
{
@ -80,8 +89,9 @@ bool AnalyzerSet::AddMod::Perform(AnalyzerSet* set)
{
if ( set->analyzer_map.Lookup(key) )
{
DBG_LOG(DBG_FILE_ANALYSIS, "Add analyzer %d skipped for file id"
" %s: already exists", a->Tag(), a->GetFile()->GetID().c_str());
DBG_LOG(DBG_FILE_ANALYSIS, "Add analyzer %s skipped for file id"
" %s: already exists", file_mgr->GetComponentName(a->Tag()),
a->GetFile()->GetID().c_str());
Abort();
return true;
@ -91,12 +101,12 @@ bool AnalyzerSet::AddMod::Perform(AnalyzerSet* set)
return true;
}
bool AnalyzerSet::Remove(const RecordVal* args)
bool AnalyzerSet::Remove(file_analysis::Tag tag, RecordVal* args)
{
return Remove(file_analysis::Analyzer::ArgsTag(args), GetKey(args));
return Remove(tag, GetKey(tag, args));
}
bool AnalyzerSet::Remove(FA_Tag tag, HashKey* key)
bool AnalyzerSet::Remove(file_analysis::Tag tag, HashKey* key)
{
file_analysis::Analyzer* a =
(file_analysis::Analyzer*) analyzer_map.Remove(key);
@ -105,22 +115,22 @@ bool AnalyzerSet::Remove(FA_Tag tag, HashKey* key)
if ( ! a )
{
DBG_LOG(DBG_FILE_ANALYSIS, "Skip remove analyzer %d for file id %s",
tag, file->GetID().c_str());
DBG_LOG(DBG_FILE_ANALYSIS, "Skip remove analyzer %s for file id %s",
file_mgr->GetComponentName(tag), file->GetID().c_str());
return false;
}
DBG_LOG(DBG_FILE_ANALYSIS, "Remove analyzer %d for file id %s", a->Tag(),
DBG_LOG(DBG_FILE_ANALYSIS, "Remove analyzer %s for file id %s",
file_mgr->GetComponentName(tag),
file->GetID().c_str());
delete a;
return true;
}
bool AnalyzerSet::QueueRemove(const RecordVal* args)
bool AnalyzerSet::QueueRemove(file_analysis::Tag tag, RecordVal* args)
{
HashKey* key = GetKey(args);
FA_Tag tag = file_analysis::Analyzer::ArgsTag(args);
HashKey* key = GetKey(tag, args);
mod_queue.push(new RemoveMod(tag, key));
@ -132,24 +142,28 @@ bool AnalyzerSet::RemoveMod::Perform(AnalyzerSet* set)
return set->Remove(tag, key);
}
HashKey* AnalyzerSet::GetKey(const RecordVal* args) const
HashKey* AnalyzerSet::GetKey(file_analysis::Tag t, RecordVal* args) const
{
HashKey* key = analyzer_hash->ComputeHash(args, 1);
ListVal* lv = new ListVal(TYPE_ANY);
lv->Append(t.AsEnumVal()->Ref());
lv->Append(args->Ref());
HashKey* key = analyzer_hash->ComputeHash(lv, 1);
Unref(lv);
if ( ! key )
reporter->InternalError("AnalyzerArgs type mismatch");
return key;
}
file_analysis::Analyzer* AnalyzerSet::InstantiateAnalyzer(RecordVal* args) const
file_analysis::Analyzer* AnalyzerSet::InstantiateAnalyzer(Tag tag,
RecordVal* args) const
{
FA_Tag tag = file_analysis::Analyzer::ArgsTag(args);
file_analysis::Analyzer* a = file_mgr->InstantiateAnalyzer(tag, args, file);
if ( ! a )
{
reporter->Error("Failed file analyzer %s instantiation for file id %s",
file_mgr->GetAnalyzerName(tag), file->GetID().c_str());
file_mgr->GetComponentName(tag), file->GetID().c_str());
return 0;
}
@ -158,8 +172,8 @@ file_analysis::Analyzer* AnalyzerSet::InstantiateAnalyzer(RecordVal* args) const
void AnalyzerSet::Insert(file_analysis::Analyzer* a, HashKey* key)
{
DBG_LOG(DBG_FILE_ANALYSIS, "Add analyzer %d for file id %s", a->Tag(),
file->GetID().c_str());
DBG_LOG(DBG_FILE_ANALYSIS, "Add analyzer %s for file id %s",
file_mgr->GetComponentName(a->Tag()), file->GetID().c_str());
analyzer_map.Insert(key, a);
delete key;
}

View file

@ -9,6 +9,7 @@
#include "Dict.h"
#include "CompHash.h"
#include "Val.h"
#include "Tag.h"
namespace file_analysis {
@ -36,33 +37,45 @@ public:
*/
~AnalyzerSet();
/**
* Looks up an analyzer by its tag and arguments.
* @param tag an analyzer tag.
* @param args an \c AnalyzerArgs record.
* @return pointer to an analyzer instance, or a null pointer if not found.
*/
Analyzer* Find(file_analysis::Tag tag, RecordVal* args);
/**
* Attach an analyzer to #file immediately.
* @param tag the analyzer tag of the file analyzer to add.
* @param args an \c AnalyzerArgs value which specifies an analyzer.
* @return true if analyzer was instantiated/attached, else false.
*/
bool Add(RecordVal* args);
bool Add(file_analysis::Tag tag, RecordVal* args);
/**
* Queue the attachment of an analyzer to #file.
* @param tag the analyzer tag of the file analyzer to add.
* @param args an \c AnalyzerArgs value which specifies an analyzer.
* @return true if analyzer was able to be instantiated, else false.
*/
bool QueueAdd(RecordVal* args);
bool QueueAdd(file_analysis::Tag tag, RecordVal* args);
/**
* Remove an analyzer from #file immediately.
* @param tag the analyzer tag of the file analyzer to remove.
* @param args an \c AnalyzerArgs value which specifies an analyzer.
* @return false if analyzer didn't exist and so wasn't removed, else true.
*/
bool Remove(const RecordVal* args);
bool Remove(file_analysis::Tag tag, RecordVal* args);
/**
* Queue the removal of an analyzer from #file.
* @param tag the analyzer tag of the file analyzer to remove.
* @param args an \c AnalyzerArgs value which specifies an analyzer.
* @return true if analyzer exists at time of call, else false;
*/
bool QueueRemove(const RecordVal* args);
bool QueueRemove(file_analysis::Tag tag, RecordVal* args);
/**
* Perform all queued modifications to the current analyzer set.
@ -91,17 +104,20 @@ protected:
/**
* Get a hash key which represents an analyzer instance.
* @param tag the file analyzer tag.
* @param args an \c AnalyzerArgs value which specifies an analyzer.
* @return the hash key calculated from \a args
*/
HashKey* GetKey(const RecordVal* args) const;
HashKey* GetKey(file_analysis::Tag tag, RecordVal* args) const;
/**
* Create an instance of a file analyzer.
* @param tag the tag of a file analyzer.
* @param args an \c AnalyzerArgs value which specifies an analyzer.
* @return a new file analyzer instance.
*/
file_analysis::Analyzer* InstantiateAnalyzer(RecordVal* args) const;
file_analysis::Analyzer* InstantiateAnalyzer(file_analysis::Tag tag,
RecordVal* args) const;
/**
* Insert an analyzer instance in to the set.
@ -116,7 +132,7 @@ protected:
* just used for debugging messages.
* @param key the hash key which represents the analyzer's \c AnalyzerArgs.
*/
bool Remove(FA_Tag tag, HashKey* key);
bool Remove(file_analysis::Tag tag, HashKey* key);
private:
@ -175,14 +191,14 @@ private:
* @param arg_a an analyzer instance to add to an analyzer set.
* @param arg_key hash key representing the analyzer's \c AnalyzerArgs.
*/
RemoveMod(FA_Tag arg_tag, HashKey* arg_key)
RemoveMod(file_analysis::Tag arg_tag, HashKey* arg_key)
: Modification(), tag(arg_tag), key(arg_key) {}
virtual ~RemoveMod() {}
virtual bool Perform(AnalyzerSet* set);
virtual void Abort() { delete key; }
protected:
FA_Tag tag;
file_analysis::Tag tag;
HashKey* key;
};

View file

@ -11,9 +11,10 @@ set(file_analysis_SRCS
Manager.cc
File.cc
FileTimer.cc
Analyzer.h
Analyzer.cc
AnalyzerSet.cc
Component.cc
Tag.cc
)
bif_target(file_analysis.bif)

View file

@ -8,26 +8,22 @@
using namespace file_analysis;
analyzer::Tag::type_t Component::type_counter = 0;
Component::Component(const char* arg_name, factory_callback arg_factory,
analyzer::Tag::subtype_t arg_subtype)
: plugin::Component(plugin::component::FILE_ANALYZER)
Component::Component(const char* arg_name, factory_callback arg_factory)
: plugin::Component(plugin::component::FILE_ANALYZER),
plugin::TaggedComponent<file_analysis::Tag>()
{
name = copy_string(arg_name);
canon_name = canonify_name(arg_name);
factory = arg_factory;
tag = analyzer::Tag(++type_counter, arg_subtype);
}
Component::Component(const Component& other)
: plugin::Component(Type())
: plugin::Component(Type()),
plugin::TaggedComponent<file_analysis::Tag>(other)
{
name = copy_string(other.name);
canon_name = copy_string(other.canon_name);
factory = other.factory;
tag = other.tag;
}
Component::~Component()
@ -36,11 +32,6 @@ Component::~Component()
delete [] canon_name;
}
analyzer::Tag Component::Tag() const
{
return tag;
}
void Component::Describe(ODesc* d) const
{
plugin::Component::Describe(d);
@ -58,11 +49,12 @@ void Component::Describe(ODesc* d) const
Component& Component::operator=(const Component& other)
{
plugin::TaggedComponent<file_analysis::Tag>::operator=(other);
if ( &other != this )
{
name = copy_string(other.name);
factory = other.factory;
tag = other.tag;
}
return *this;

View file

@ -3,8 +3,9 @@
#ifndef FILE_ANALYZER_PLUGIN_COMPONENT_H
#define FILE_ANALYZER_PLUGIN_COMPONENT_H
#include "analyzer/Tag.h"
#include "Tag.h"
#include "plugin/Component.h"
#include "plugin/TaggedComponent.h"
#include "Val.h"
@ -22,7 +23,8 @@ class Analyzer;
* A plugin can provide a specific file analyzer by registering this
* analyzer component, describing the analyzer.
*/
class Component : public plugin::Component {
class Component : public plugin::Component,
public plugin::TaggedComponent<file_analysis::Tag> {
public:
typedef Analyzer* (*factory_callback)(RecordVal* args, File* file);
@ -38,15 +40,8 @@ public:
* from file_analysis::Analyzer. This is typically a static \c
* Instatiate() method inside the class that just allocates and
* returns a new instance.
*
* @param subtype A subtype associated with this component that
* further distinguishes it. The subtype will be integrated into
* the analyzer::Tag that the manager associates with this analyzer,
* and analyzer instances can accordingly access it via analyzer::Tag().
* If not used, leave at zero.
*/
Component(const char* name, factory_callback factory,
analyzer::Tag::subtype_t subtype = 0);
Component(const char* name, factory_callback factory);
/**
* Copy constructor.
@ -79,13 +74,6 @@ public:
*/
factory_callback Factory() const { return factory; }
/**
* Returns the analyzer's tag. Note that this is automatically
* generated for each new Components, and hence unique across all of
* them.
*/
analyzer::Tag Tag() const;
/**
* Generates a human-readable description of the component's main
* parameters. This goes into the output of \c "bro -NN".
@ -98,10 +86,6 @@ private:
const char* name; // The analyzer's name.
const char* canon_name; // The analyzer's canonical name.
factory_callback factory; // The analyzer's factory callback.
analyzer::Tag tag; // The automatically assigned analyzer tag.
// Global counter used to generate unique tags.
static analyzer::Tag::type_t type_counter;
};
}

View file

@ -10,10 +10,13 @@
#include "Val.h"
#include "Type.h"
#include "Event.h"
#include "RuleMatcher.h"
#include "analyzer/Analyzer.h"
#include "analyzer/Manager.h"
#include "analyzer/extract/Extract.h"
using namespace file_analysis;
static Val* empty_connection_table()
@ -50,6 +53,7 @@ int File::timeout_interval_idx = -1;
int File::bof_buffer_size_idx = -1;
int File::bof_buffer_idx = -1;
int File::mime_type_idx = -1;
int File::mime_types_idx = -1;
void File::StaticInit()
{
@ -70,12 +74,14 @@ void File::StaticInit()
bof_buffer_size_idx = Idx("bof_buffer_size");
bof_buffer_idx = Idx("bof_buffer");
mime_type_idx = Idx("mime_type");
mime_types_idx = Idx("mime_types");
}
File::File(const string& file_id, Connection* conn, analyzer::Tag tag,
bool is_orig)
: id(file_id), val(0), postpone_timeout(false), first_chunk(true),
missed_bof(false), need_reassembly(false), done(false), analyzers(this)
missed_bof(false), need_reassembly(false), done(false),
did_file_new_event(false), analyzers(this)
{
StaticInit();
@ -87,9 +93,9 @@ File::File(const string& file_id, Connection* conn, analyzer::Tag tag,
if ( conn )
{
// add source, connection, is_orig fields
SetSource(analyzer_mgr->GetAnalyzerName(tag));
SetSource(analyzer_mgr->GetComponentName(tag));
val->Assign(is_orig_idx, new Val(is_orig, TYPE_BOOL));
UpdateConnectionFields(conn);
UpdateConnectionFields(conn, is_orig);
}
UpdateLastActivityTime();
@ -99,6 +105,12 @@ File::~File()
{
DBG_LOG(DBG_FILE_ANALYSIS, "Destroying File object %s", id.c_str());
Unref(val);
while ( ! fonc_queue.empty() )
{
delete_vals(fonc_queue.front().second);
fonc_queue.pop();
}
}
void File::UpdateLastActivityTime()
@ -111,18 +123,15 @@ double File::GetLastActivityTime() const
return val->Lookup(last_active_idx)->AsTime();
}
void File::UpdateConnectionFields(Connection* conn)
void File::UpdateConnectionFields(Connection* conn, bool is_orig)
{
if ( ! conn )
return;
Val* conns = val->Lookup(conns_idx);
bool is_first = false;
if ( ! conns )
{
is_first = true;
conns = empty_connection_table();
val->Assign(conns_idx, conns);
}
@ -133,12 +142,18 @@ void File::UpdateConnectionFields(Connection* conn)
Val* conn_val = conn->BuildConnVal();
conns->AsTableVal()->Assign(idx, conn_val);
if ( ! is_first && FileEventAvailable(file_over_new_connection) )
if ( FileEventAvailable(file_over_new_connection) )
{
val_list* vl = new val_list();
vl->append(val->Ref());
vl->append(conn_val->Ref());
FileEvent(file_over_new_connection, vl);
vl->append(new Val(is_orig, TYPE_BOOL));
if ( did_file_new_event )
FileEvent(file_over_new_connection, vl);
else
fonc_queue.push(pair<EventHandlerPtr, val_list*>(
file_over_new_connection, vl));
}
}
@ -192,6 +207,22 @@ void File::SetTimeoutInterval(double interval)
val->Assign(timeout_interval_idx, new Val(interval, TYPE_INTERVAL));
}
bool File::SetExtractionLimit(RecordVal* args, uint64 bytes)
{
Analyzer* a = analyzers.Find(file_mgr->GetComponentTag("EXTRACT"), args);
if ( ! a )
return false;
Extract* e = dynamic_cast<Extract*>(a);
if ( ! e )
return false;
e->SetLimit(bytes);
return true;
}
void File::IncrementByteCount(uint64 size, int field_idx)
{
uint64 old = LookupFieldDefaultCount(field_idx);
@ -220,14 +251,14 @@ void File::ScheduleInactivityTimer() const
timer_mgr->Add(new FileTimer(network_time, id, GetTimeoutInterval()));
}
bool File::AddAnalyzer(RecordVal* args)
bool File::AddAnalyzer(file_analysis::Tag tag, RecordVal* args)
{
return done ? false : analyzers.QueueAdd(args);
return done ? false : analyzers.QueueAdd(tag, args);
}
bool File::RemoveAnalyzer(const RecordVal* args)
bool File::RemoveAnalyzer(file_analysis::Tag tag, RecordVal* args)
{
return done ? false : analyzers.QueueRemove(args);
return done ? false : analyzers.QueueRemove(tag, args);
}
bool File::BufferBOF(const u_char* data, uint64 len)
@ -251,20 +282,18 @@ bool File::BufferBOF(const u_char* data, uint64 len)
bool File::DetectMIME(const u_char* data, uint64 len)
{
const char* mime = bro_magic_buffer(magic_mime_cookie, data, len);
RuleMatcher::MIME_Matches matches;
len = min(len, LookupFieldDefaultCount(bof_buffer_size_idx));
file_mgr->DetectMIME(data, len, &matches);
if ( mime )
{
const char* mime_end = strchr(mime, ';');
if ( matches.empty() )
return false;
if ( mime_end )
// strip off charset
val->Assign(mime_type_idx, new StringVal(mime_end - mime, mime));
else
val->Assign(mime_type_idx, new StringVal(mime));
}
val->Assign(mime_type_idx,
new StringVal(*(matches.begin()->second.begin())));
val->Assign(mime_types_idx, file_analysis::GenMIMEMatchesVal(matches));
return mime;
return true;
}
void File::ReplayBOF()
@ -310,7 +339,7 @@ void File::DataIn(const u_char* data, uint64 len, uint64 offset)
while ( (a = analyzers.NextEntry(c)) )
{
if ( ! a->DeliverChunk(data, len, offset) )
analyzers.QueueRemove(a->Args());
analyzers.QueueRemove(a->Tag(), a->Args());
}
analyzers.DrainModifications();
@ -345,7 +374,7 @@ void File::DataIn(const u_char* data, uint64 len)
{
if ( ! a->DeliverStream(data, len) )
{
analyzers.QueueRemove(a->Args());
analyzers.QueueRemove(a->Tag(), a->Args());
continue;
}
@ -353,7 +382,7 @@ void File::DataIn(const u_char* data, uint64 len)
LookupFieldDefaultCount(missing_bytes_idx);
if ( ! a->DeliverChunk(data, len, offset) )
analyzers.QueueRemove(a->Args());
analyzers.QueueRemove(a->Tag(), a->Args());
}
analyzers.DrainModifications();
@ -378,7 +407,7 @@ void File::EndOfFile()
while ( (a = analyzers.NextEntry(c)) )
{
if ( ! a->EndOfFile() )
analyzers.QueueRemove(a->Args());
analyzers.QueueRemove(a->Tag(), a->Args());
}
FileEvent(file_state_remove);
@ -400,7 +429,7 @@ void File::Gap(uint64 offset, uint64 len)
while ( (a = analyzers.NextEntry(c)) )
{
if ( ! a->Undelivered(offset, len) )
analyzers.QueueRemove(a->Args());
analyzers.QueueRemove(a->Tag(), a->Args());
}
if ( FileEventAvailable(file_gap) )
@ -431,11 +460,30 @@ void File::FileEvent(EventHandlerPtr h)
FileEvent(h, vl);
}
static void flush_file_event_queue(queue<pair<EventHandlerPtr, val_list*> >& q)
{
while ( ! q.empty() )
{
pair<EventHandlerPtr, val_list*> p = q.front();
mgr.QueueEvent(p.first, p.second);
q.pop();
}
}
void File::FileEvent(EventHandlerPtr h, val_list* vl)
{
if ( h == file_state_remove )
flush_file_event_queue(fonc_queue);
mgr.QueueEvent(h, vl);
if ( h == file_new || h == file_timeout )
if ( h == file_new )
{
did_file_new_event = true;
flush_file_event_queue(fonc_queue);
}
if ( h == file_new || h == file_timeout || h == file_extraction_limit )
{
// immediate feedback is required for these events.
mgr.Drain();

View file

@ -3,11 +3,14 @@
#ifndef FILE_ANALYSIS_FILE_H
#define FILE_ANALYSIS_FILE_H
#include <queue>
#include <string>
#include <utility>
#include <vector>
#include "Conn.h"
#include "Val.h"
#include "Tag.h"
#include "AnalyzerSet.h"
#include "BroString.h"
@ -53,6 +56,14 @@ public:
*/
void SetTimeoutInterval(double interval);
/**
* Change the maximum size that an attached extraction analyzer is allowed.
* @param args the file extraction analyzer whose limit needs changed.
* @param bytes new limit.
* @return false if no extraction analyzer is active, else true.
*/
bool SetExtractionLimit(RecordVal* args, uint64 bytes);
/**
* @return value of the "id" field from #val record.
*/
@ -92,17 +103,19 @@ public:
/**
* Queues attaching an analyzer. Only one analyzer per type can be attached
* at a time unless the arguments differ.
* @param tag the analyzer tag of the file analyzer to add.
* @param args an \c AnalyzerArgs value representing a file analyzer.
* @return false if analyzer can't be instantiated, else true.
*/
bool AddAnalyzer(RecordVal* args);
bool AddAnalyzer(file_analysis::Tag tag, RecordVal* args);
/**
* Queues removal of an analyzer.
* @param tag the analyzer tag of the file analyzer to remove.
* @param args an \c AnalyzerArgs value representing a file analyzer.
* @return true if analyzer was active at time of call, else false.
*/
bool RemoveAnalyzer(const RecordVal* args);
bool RemoveAnalyzer(file_analysis::Tag tag, RecordVal* args);
/**
* Pass in non-sequential data and deliver to attached analyzers.
@ -171,8 +184,9 @@ protected:
* Updates the "conn_ids" and "conn_uids" fields in #val record with the
* \c conn_id and UID taken from \a conn.
* @param conn the connection over which a part of the file has been seen.
* @param is_orig true if the connection originator is sending the file.
*/
void UpdateConnectionFields(Connection* conn);
void UpdateConnectionFields(Connection* conn, bool is_orig);
/**
* Increment a byte count field of #val record by \a size.
@ -211,11 +225,12 @@ protected:
void ReplayBOF();
/**
* Does mime type detection and assigns type (if available) to \c mime_type
* Does mime type detection via file magic signatures and assigns
* strongest matching mime type (if available) to \c mime_type
* field in #val.
* @param data pointer to a chunk of file data.
* @param len number of bytes in the data chunk.
* @return whether mime type was available.
* @return whether a mime type match was found.
*/
bool DetectMIME(const u_char* data, uint64 len);
@ -239,7 +254,9 @@ private:
bool missed_bof; /**< Flags that we missed start of file. */
bool need_reassembly; /**< Whether file stream reassembly is needed. */
bool done; /**< If this object is about to be deleted. */
bool did_file_new_event; /**< Whether the file_new event has been done. */
AnalyzerSet analyzers; /**< A set of attached file analyzer. */
queue<pair<EventHandlerPtr, val_list*> > fonc_queue;
struct BOF_Buffer {
BOF_Buffer() : full(false), replayed(false), size(0) {}
@ -266,6 +283,7 @@ private:
static int bof_buffer_size_idx;
static int bof_buffer_idx;
static int mime_type_idx;
static int mime_types_idx;
};
} // namespace file_analysis

View file

@ -14,7 +14,7 @@ FileTimer::FileTimer(double t, const string& id, double interval)
void FileTimer::Dispatch(double t, int is_expire)
{
File* file = file_mgr->Lookup(file_id);
File* file = file_mgr->LookupFile(file_id);
if ( ! file )
return;

View file

@ -9,6 +9,7 @@
#include "Analyzer.h"
#include "Var.h"
#include "Event.h"
#include "UID.h"
#include "plugin/Manager.h"
@ -18,15 +19,31 @@ TableVal* Manager::disabled = 0;
string Manager::salt;
Manager::Manager()
: plugin::ComponentManager<file_analysis::Tag,
file_analysis::Component>("Files"),
id_map(), ignored(), current_file_id(), magic_state()
{
tag_enum_type = new EnumType("FileAnalysis::Tag");
::ID* id = install_ID("Tag", "FileAnalysis", true, true);
add_type(id, tag_enum_type, 0, 0);
}
Manager::~Manager()
{
Terminate();
// Have to assume that too much of Bro has been shutdown by this point
// to do anything more than reclaim memory.
File* f;
bool* b;
IterCookie* it = id_map.InitForIteration();
while ( (f = id_map.NextEntry(it)) )
delete f;
it = ignored.InitForIteration();
while( (b = ignored.NextEntry(it)) )
delete b;
delete magic_state;
}
void Manager::InitPreScript()
@ -35,58 +52,51 @@ void Manager::InitPreScript()
for ( std::list<Component*>::const_iterator i = analyzers.begin();
i != analyzers.end(); ++i )
RegisterAnalyzerComponent(*i);
}
void Manager::RegisterAnalyzerComponent(Component* component)
{
const char* cname = component->CanonicalName();
if ( tag_enum_type->Lookup("FileAnalysis", cname) != -1 )
reporter->FatalError("File Analyzer %s defined more than once", cname);
DBG_LOG(DBG_FILE_ANALYSIS, "Registering analyzer %s (tag %s)",
component->Name(), component->Tag().AsString().c_str());
analyzers_by_name.insert(std::make_pair(cname, component));
analyzers_by_tag.insert(std::make_pair(component->Tag(), component));
analyzers_by_val.insert(std::make_pair(
component->Tag().AsEnumVal()->InternalInt(), component));
string id = fmt("ANALYZER_%s", cname);
tag_enum_type->AddName("FileAnalysis", id.c_str(),
component->Tag().AsEnumVal()->InternalInt(), true);
RegisterComponent(*i, "ANALYZER_");
}
void Manager::InitPostScript()
{
}
void Manager::InitMagic()
{
delete magic_state;
magic_state = rule_matcher->InitFileMagic();
}
void Manager::Terminate()
{
vector<string> keys;
for ( IDMap::iterator it = id_map.begin(); it != id_map.end(); ++it )
keys.push_back(it->first);
IterCookie* it = id_map.InitForIteration();
HashKey* key;
while ( id_map.NextEntry(key, it) )
{
keys.push_back(string(static_cast<const char*>(key->Key()),
key->Size()));
delete key;
}
for ( size_t i = 0; i < keys.size(); ++i )
Timeout(keys[i], true);
mgr.Drain();
}
string Manager::HashHandle(const string& handle) const
{
if ( salt.empty() )
salt = BifConst::FileAnalysis::salt->CheckString();
salt = BifConst::Files::salt->CheckString();
char tmp[20];
uint64 hash[2];
string msg(handle + salt);
MD5(reinterpret_cast<const u_char*>(msg.data()), msg.size(),
reinterpret_cast<u_char*>(hash));
uitoa_n(hash[0], tmp, sizeof(tmp), 62);
return tmp;
return Bro::UID(bits_per_uid, hash, 2).Base62("F");
}
void Manager::SetHandle(const string& handle)
@ -97,36 +107,47 @@ void Manager::SetHandle(const string& handle)
current_file_id = HashHandle(handle);
}
void Manager::DataIn(const u_char* data, uint64 len, uint64 offset,
analyzer::Tag tag, Connection* conn, bool is_orig)
string Manager::DataIn(const u_char* data, uint64 len, uint64 offset,
analyzer::Tag tag, Connection* conn, bool is_orig,
const string& precomputed_id)
{
GetFileHandle(tag, conn, is_orig);
File* file = GetFile(current_file_id, conn, tag, is_orig);
string id = precomputed_id.empty() ? GetFileID(tag, conn, is_orig) : precomputed_id;
File* file = GetFile(id, conn, tag, is_orig);
if ( ! file )
return;
return "";
file->DataIn(data, len, offset);
if ( file->IsComplete() )
{
RemoveFile(file->GetID());
return "";
}
return id;
}
void Manager::DataIn(const u_char* data, uint64 len, analyzer::Tag tag,
Connection* conn, bool is_orig)
string Manager::DataIn(const u_char* data, uint64 len, analyzer::Tag tag,
Connection* conn, bool is_orig, const string& precomputed_id)
{
GetFileHandle(tag, conn, is_orig);
string id = precomputed_id.empty() ? GetFileID(tag, conn, is_orig) : precomputed_id;
// Sequential data input shouldn't be going over multiple conns, so don't
// do the check to update connection set.
File* file = GetFile(current_file_id, conn, tag, is_orig, false);
File* file = GetFile(id, conn, tag, is_orig, false);
if ( ! file )
return;
return "";
file->DataIn(data, len);
if ( file->IsComplete() )
{
RemoveFile(file->GetID());
return "";
}
return id;
}
void Manager::DataIn(const u_char* data, uint64 len, const string& file_id,
@ -155,8 +176,7 @@ void Manager::EndOfFile(analyzer::Tag tag, Connection* conn)
void Manager::EndOfFile(analyzer::Tag tag, Connection* conn, bool is_orig)
{
// Don't need to create a file if we're just going to remove it right away.
GetFileHandle(tag, conn, is_orig);
RemoveFile(current_file_id);
RemoveFile(GetFileID(tag, conn, is_orig));
}
void Manager::EndOfFile(const string& file_id)
@ -164,36 +184,42 @@ void Manager::EndOfFile(const string& file_id)
RemoveFile(file_id);
}
void Manager::Gap(uint64 offset, uint64 len, analyzer::Tag tag,
Connection* conn, bool is_orig)
string Manager::Gap(uint64 offset, uint64 len, analyzer::Tag tag,
Connection* conn, bool is_orig, const string& precomputed_id)
{
GetFileHandle(tag, conn, is_orig);
File* file = GetFile(current_file_id, conn, tag, is_orig);
string id = precomputed_id.empty() ? GetFileID(tag, conn, is_orig) : precomputed_id;
File* file = GetFile(id, conn, tag, is_orig);
if ( ! file )
return;
return "";
file->Gap(offset, len);
return id;
}
void Manager::SetSize(uint64 size, analyzer::Tag tag, Connection* conn,
bool is_orig)
string Manager::SetSize(uint64 size, analyzer::Tag tag, Connection* conn,
bool is_orig, const string& precomputed_id)
{
GetFileHandle(tag, conn, is_orig);
File* file = GetFile(current_file_id, conn, tag, is_orig);
string id = precomputed_id.empty() ? GetFileID(tag, conn, is_orig) : precomputed_id;
File* file = GetFile(id, conn, tag, is_orig);
if ( ! file )
return;
return "";
file->SetTotalBytes(size);
if ( file->IsComplete() )
{
RemoveFile(file->GetID());
return "";
}
return id;
}
bool Manager::SetTimeoutInterval(const string& file_id, double interval) const
{
File* file = Lookup(file_id);
File* file = LookupFile(file_id);
if ( ! file )
return false;
@ -205,24 +231,37 @@ bool Manager::SetTimeoutInterval(const string& file_id, double interval) const
return true;
}
bool Manager::AddAnalyzer(const string& file_id, RecordVal* args) const
bool Manager::SetExtractionLimit(const string& file_id, RecordVal* args,
uint64 n) const
{
File* file = Lookup(file_id);
File* file = LookupFile(file_id);
if ( ! file )
return false;
return file->AddAnalyzer(args);
return file->SetExtractionLimit(args, n);
}
bool Manager::RemoveAnalyzer(const string& file_id, const RecordVal* args) const
bool Manager::AddAnalyzer(const string& file_id, file_analysis::Tag tag,
RecordVal* args) const
{
File* file = Lookup(file_id);
File* file = LookupFile(file_id);
if ( ! file )
return false;
return file->RemoveAnalyzer(args);
return file->AddAnalyzer(tag, args);
}
bool Manager::RemoveAnalyzer(const string& file_id, file_analysis::Tag tag,
RecordVal* args) const
{
File* file = LookupFile(file_id);
if ( ! file )
return false;
return file->RemoveAnalyzer(tag, args);
}
File* Manager::GetFile(const string& file_id, Connection* conn,
@ -234,11 +273,12 @@ File* Manager::GetFile(const string& file_id, Connection* conn,
if ( IsIgnored(file_id) )
return 0;
File* rval = id_map[file_id];
File* rval = id_map.Lookup(file_id.c_str());
if ( ! rval )
{
rval = id_map[file_id] = new File(file_id, conn, tag, is_orig);
rval = new File(file_id, conn, tag, is_orig);
id_map.Insert(file_id.c_str(), rval);
rval->ScheduleInactivityTimer();
if ( IsIgnored(file_id) )
@ -249,25 +289,20 @@ File* Manager::GetFile(const string& file_id, Connection* conn,
rval->UpdateLastActivityTime();
if ( update_conn )
rval->UpdateConnectionFields(conn);
rval->UpdateConnectionFields(conn, is_orig);
}
return rval;
}
File* Manager::Lookup(const string& file_id) const
File* Manager::LookupFile(const string& file_id) const
{
IDMap::const_iterator it = id_map.find(file_id);
if ( it == id_map.end() )
return 0;
return it->second;
return id_map.Lookup(file_id.c_str());
}
void Manager::Timeout(const string& file_id, bool is_terminating)
{
File* file = Lookup(file_id);
File* file = LookupFile(file_id);
if ( ! file )
return;
@ -293,48 +328,49 @@ void Manager::Timeout(const string& file_id, bool is_terminating)
bool Manager::IgnoreFile(const string& file_id)
{
if ( id_map.find(file_id) == id_map.end() )
if ( ! id_map.Lookup(file_id.c_str()) )
return false;
DBG_LOG(DBG_FILE_ANALYSIS, "Ignore FileID %s", file_id.c_str());
ignored.insert(file_id);
delete ignored.Insert(file_id.c_str(), new bool);
return true;
}
bool Manager::RemoveFile(const string& file_id)
{
IDMap::iterator it = id_map.find(file_id);
HashKey key(file_id.c_str());
// Can't remove from the dictionary/map right away as invoking EndOfFile
// may cause some events to be executed which actually depend on the file
// still being in the dictionary/map.
File* f = static_cast<File*>(id_map.Lookup(&key));
if ( it == id_map.end() )
if ( ! f )
return false;
DBG_LOG(DBG_FILE_ANALYSIS, "Remove FileID %s", file_id.c_str());
it->second->EndOfFile();
delete it->second;
id_map.erase(file_id);
ignored.erase(file_id);
f->EndOfFile();
delete f;
id_map.Remove(&key);
delete static_cast<bool*>(ignored.Remove(&key));
return true;
}
bool Manager::IsIgnored(const string& file_id)
{
return ignored.find(file_id) != ignored.end();
return ignored.Lookup(file_id.c_str()) != 0;
}
void Manager::GetFileHandle(analyzer::Tag tag, Connection* c, bool is_orig)
string Manager::GetFileID(analyzer::Tag tag, Connection* c, bool is_orig)
{
current_file_id.clear();
if ( IsDisabled(tag) )
return;
return "";
if ( ! get_file_handle )
return;
return "";
EnumVal* tagval = tag.AsEnumVal();
Ref(tagval);
@ -346,12 +382,13 @@ void Manager::GetFileHandle(analyzer::Tag tag, Connection* c, bool is_orig)
mgr.QueueEvent(get_file_handle, vl);
mgr.Drain(); // need file handle immediately so we don't have to buffer data
return current_file_id;
}
bool Manager::IsDisabled(analyzer::Tag tag)
{
if ( ! disabled )
disabled = internal_const_val("FileAnalysis::disable")->AsTableVal();
disabled = internal_const_val("Files::disable")->AsTableVal();
Val* index = new Val(tag, TYPE_COUNT);
Val* yield = disabled->Lookup(index);
@ -366,30 +403,68 @@ bool Manager::IsDisabled(analyzer::Tag tag)
return rval;
}
Analyzer* Manager::InstantiateAnalyzer(int tag, RecordVal* args, File* f) const
Analyzer* Manager::InstantiateAnalyzer(Tag tag, RecordVal* args, File* f) const
{
analyzer_map_by_val::const_iterator it = analyzers_by_val.find(tag);
Component* c = Lookup(tag);
if ( it == analyzers_by_val.end() )
reporter->InternalError("cannot instantiate unknown file analyzer: %d",
tag);
Component* c = it->second;
if ( ! c )
{
reporter->InternalWarning(
"unknown file analyzer instantiation request: %s",
tag.AsString().c_str());
return 0;
}
if ( ! c->Factory() )
reporter->InternalError("file analyzer %s cannot be instantiated "
{
reporter->InternalWarning("file analyzer %s cannot be instantiated "
"dynamically", c->CanonicalName());
return 0;
}
return c->Factory()(args, f);
}
const char* Manager::GetAnalyzerName(int tag) const
RuleMatcher::MIME_Matches* Manager::DetectMIME(const u_char* data, uint64 len,
RuleMatcher::MIME_Matches* rval) const
{
analyzer_map_by_val::const_iterator it = analyzers_by_val.find(tag);
if ( ! magic_state )
reporter->InternalError("file magic signature state not initialized");
if ( it == analyzers_by_val.end() )
reporter->InternalError("cannot get name of unknown file analyzer: %d",
tag);
return it->second->CanonicalName();
rval = rule_matcher->Match(magic_state, data, len, rval);
rule_matcher->ClearFileMagicState(magic_state);
return rval;
}
string Manager::DetectMIME(const u_char* data, uint64 len) const
{
RuleMatcher::MIME_Matches matches;
DetectMIME(data, len, &matches);
if ( matches.empty() )
return "";
return *(matches.begin()->second.begin());
}
VectorVal* file_analysis::GenMIMEMatchesVal(const RuleMatcher::MIME_Matches& m)
{
VectorVal* rval = new VectorVal(mime_matches);
for ( RuleMatcher::MIME_Matches::const_iterator it = m.begin();
it != m.end(); ++it )
{
RecordVal* element = new RecordVal(mime_match);
for ( set<string>::const_iterator it2 = it->second.begin();
it2 != it->second.end(); ++it2 )
{
element->Assign(0, new Val(it->first, TYPE_INT));
element->Assign(1, new StringVal(*it2));
}
rval->Assign(rval->Size(), element);
}
return rval;
}

View file

@ -4,31 +4,35 @@
#define FILE_ANALYSIS_MANAGER_H
#include <string>
#include <map>
#include <set>
#include <queue>
#include "Dict.h"
#include "Net.h"
#include "Conn.h"
#include "Val.h"
#include "Analyzer.h"
#include "Timer.h"
#include "EventHandler.h"
#include "RuleMatcher.h"
#include "File.h"
#include "FileTimer.h"
#include "Component.h"
#include "Tag.h"
#include "plugin/ComponentManager.h"
#include "analyzer/Tag.h"
#include "file_analysis/file_analysis.bif.h"
namespace file_analysis {
declare(PDict,bool);
declare(PDict,File);
/**
* Main entry point for interacting with file analysis.
*/
class Manager {
class Manager : public plugin::ComponentManager<Tag, Component> {
public:
/**
@ -53,6 +57,12 @@ public:
*/
void InitPostScript();
/**
* Initializes the state required to match against file magic signatures
* for MIME type identification.
*/
void InitMagic();
/**
* Times out any active file analysis to prepare for shutdown.
*/
@ -61,7 +71,7 @@ public:
/**
* Creates a file identifier from a unique file handle string.
* @param handle a unique string which identifies a single file.
* @return a prettified MD5 hash of \a handle, truncated to 64-bits.
* @return a prettified MD5 hash of \a handle, truncated to *bits_per_uid* bits.
*/
string HashHandle(const string& handle) const;
@ -81,9 +91,17 @@ public:
* @param conn network connection over which the file data is transferred.
* @param is_orig true if the file is being sent from connection originator
* or false if is being sent in the opposite direction.
* @param precomputed_file_id may be set to a previous return value in order to
* bypass costly file handle lookups.
* @return a unique file ID string which, in certain contexts, may be
* cached and passed back in to a subsequent function call in order
* to avoid costly file handle lookups (which have to go through
* the \c get_file_handle script-layer event). An empty string
* indicates the associate file is not going to be analyzed further.
*/
void DataIn(const u_char* data, uint64 len, uint64 offset,
analyzer::Tag tag, Connection* conn, bool is_orig);
std::string DataIn(const u_char* data, uint64 len, uint64 offset,
analyzer::Tag tag, Connection* conn, bool is_orig,
const std::string& precomputed_file_id = "");
/**
* Pass in sequential file data.
@ -93,9 +111,17 @@ public:
* @param conn network connection over which the file data is transferred.
* @param is_orig true if the file is being sent from connection originator
* or false if is being sent in the opposite direction.
* @param precomputed_file_id may be set to a previous return value in order to
* bypass costly file handle lookups.
* @return a unique file ID string which, in certain contexts, may be
* cached and passed back in to a subsequent function call in order
* to avoid costly file handle lookups (which have to go through
* the \c get_file_handle script-layer event). An empty string
* indicates the associated file is not going to be analyzed further.
*/
void DataIn(const u_char* data, uint64 len, analyzer::Tag tag,
Connection* conn, bool is_orig);
std::string DataIn(const u_char* data, uint64 len, analyzer::Tag tag,
Connection* conn, bool is_orig,
const std::string& precomputed_file_id = "");
/**
* Pass in sequential file data from external source (e.g. input framework).
@ -139,9 +165,17 @@ public:
* @param conn network connection over which the file data is transferred.
* @param is_orig true if the file is being sent from connection originator
* or false if is being sent in the opposite direction.
* @param precomputed_file_id may be set to a previous return value in order to
* bypass costly file handle lookups.
* @return a unique file ID string which, in certain contexts, may be
* cached and passed back in to a subsequent function call in order
* to avoid costly file handle lookups (which have to go through
* the \c get_file_handle script-layer event). An empty string
* indicates the associate file is not going to be analyzed further.
*/
void Gap(uint64 offset, uint64 len, analyzer::Tag tag, Connection* conn,
bool is_orig);
std::string Gap(uint64 offset, uint64 len, analyzer::Tag tag,
Connection* conn, bool is_orig,
const std::string& precomputed_file_id = "");
/**
* Provide the expected number of bytes that comprise a file.
@ -150,9 +184,16 @@ public:
* @param conn network connection over which the file data is transferred.
* @param is_orig true if the file is being sent from connection originator
* or false if is being sent in the opposite direction.
* @param precomputed_file_id may be set to a previous return value in order to
* bypass costly file handle lookups.
* @return a unique file ID string which, in certain contexts, may be
* cached and passed back in to a subsequent function call in order
* to avoid costly file handle lookups (which have to go through
* the \c get_file_handle script-layer event). An empty string
* indicates the associate file is not going to be analyzed further.
*/
void SetSize(uint64 size, analyzer::Tag tag, Connection* conn,
bool is_orig);
std::string SetSize(uint64 size, analyzer::Tag tag, Connection* conn,
bool is_orig, const std::string& precomputed_file_id = "");
/**
* Starts ignoring a file, which will finally be removed from internal
@ -172,23 +213,40 @@ public:
*/
bool SetTimeoutInterval(const string& file_id, double interval) const;
/**
* Sets a limit on the maximum size allowed for extracting the file
* to local disk;
* @param file_id the file identifier/hash.
* @param args a \c AnalyzerArgs value which describes a file analyzer,
* which should be a file extraction analyzer.
* @param n the new extraction limit, in bytes.
* @return false if file identifier and analyzer did not map to anything,
* else true.
*/
bool SetExtractionLimit(const string& file_id, RecordVal* args,
uint64 n) const;
/**
* Queue attachment of an analzer to the file identifier. Multiple
* analyzers of a given type can be attached per file identifier at a time
* as long as the arguments differ.
* @param file_id the file identifier/hash.
* @param tag the analyzer tag of the file analyzer to add.
* @param args a \c AnalyzerArgs value which describes a file analyzer.
* @return false if the analyzer failed to be instantiated, else true.
*/
bool AddAnalyzer(const string& file_id, RecordVal* args) const;
bool AddAnalyzer(const string& file_id, file_analysis::Tag tag,
RecordVal* args) const;
/**
* Queue removal of an analyzer for a given file identifier.
* @param file_id the file identifier/hash.
* @param tag the analyzer tag of the file analyzer to remove.
* @param args a \c AnalyzerArgs value which describes a file analyzer.
* @return true if the analyzer is active at the time of call, else false.
*/
bool RemoveAnalyzer(const string& file_id, const RecordVal* args) const;
bool RemoveAnalyzer(const string& file_id, file_analysis::Tag tag,
RecordVal* args) const;
/**
* Tells whether analysis for a file is active or ignored.
@ -204,21 +262,36 @@ public:
* @param f The file analzer is to be associated with.
* @return The new analyzer instance or null if tag is invalid.
*/
Analyzer* InstantiateAnalyzer(int tag, RecordVal* args, File* f) const;
Analyzer* InstantiateAnalyzer(Tag tag, RecordVal* args, File* f) const;
/**
* Translates a script-level file analyzer tag in to corresponding file
* analyzer name.
* @param tag The enum val of a file analyzer.
* @return The human-readable name of the file analyzer.
* Returns a set of all matching MIME magic signatures for a given
* chunk of data.
* @param data A chunk of bytes to match magic MIME signatures against.
* @param len The number of bytes in \a data.
* @param rval An optional pre-existing structure in which to insert
* new matches. If it's a null pointer, an object is
* allocated and returned from the method.
* @return Set of all matching file magic signatures, which may be
* an object allocated by the method if \a rval is a null pointer.
*/
const char* GetAnalyzerName(int tag) const;
RuleMatcher::MIME_Matches* DetectMIME(const u_char* data, uint64 len,
RuleMatcher::MIME_Matches* rval) const;
/**
* Returns the strongest MIME magic signature match for a given data chunk.
* @param data A chunk of bytes to match magic MIME signatures against.
* @param len The number of bytes in \a data.
* @returns The MIME type string of the strongest file magic signature
* match, or an empty string if nothing matched.
*/
std::string DetectMIME(const u_char* data, uint64 len) const;
protected:
friend class FileTimer;
typedef set<string> IDSet;
typedef map<string, File*> IDMap;
typedef PDict(bool) IDSet;
typedef PDict(File) IDMap;
/**
* Create a new file to be analyzed or retrieve an existing one.
@ -247,7 +320,7 @@ protected:
* @return the File object mapped to \a file_id, or a null pointer if no
* mapping exists.
*/
File* Lookup(const string& file_id) const;
File* LookupFile(const string& file_id) const;
/**
* Evaluate timeout policy for a file and remove the File object mapped to
@ -273,8 +346,10 @@ protected:
* @param conn network connection over which the file is transferred.
* @param is_orig true if the file is being sent from connection originator
* or false if is being sent in the opposite direction.
* @return #current_file_id, which is a hash of a unique file handle string
* set by a \c get_file_handle event handler.
*/
void GetFileHandle(analyzer::Tag tag, Connection* c, bool is_orig);
std::string GetFileID(analyzer::Tag tag, Connection* c, bool is_orig);
/**
* Check if analysis is available for files transferred over a given
@ -287,25 +362,22 @@ protected:
static bool IsDisabled(analyzer::Tag tag);
private:
typedef map<string, Component*> analyzer_map_by_name;
typedef map<analyzer::Tag, Component*> analyzer_map_by_tag;
typedef map<int, Component*> analyzer_map_by_val;
void RegisterAnalyzerComponent(Component* component);
IDMap id_map; /**< Map file ID to file_analysis::File records. */
IDSet ignored; /**< Ignored files. Will be finally removed on EOF. */
PDict(File) id_map; /**< Map file ID to file_analysis::File records. */
PDict(bool) ignored; /**< Ignored files. Will be finally removed on EOF. */
string current_file_id; /**< Hash of what get_file_handle event sets. */
EnumType* tag_enum_type; /**< File analyzer tag type. */
analyzer_map_by_name analyzers_by_name;
analyzer_map_by_tag analyzers_by_tag;
analyzer_map_by_val analyzers_by_val;
RuleFileMagicState* magic_state; /**< File magic signature match state. */
static TableVal* disabled; /**< Table of disabled analyzers. */
static string salt; /**< A salt added to file handles before hashing. */
};
/**
* Returns a script-layer value corresponding to the \c mime_matches type.
* @param m The MIME match information with which to populate the value.
*/
VectorVal* GenMIMEMatchesVal(const RuleMatcher::MIME_Matches& m);
} // namespace file_analysis
extern file_analysis::Manager* file_mgr;

24
src/file_analysis/Tag.cc Normal file
View file

@ -0,0 +1,24 @@
// See the file "COPYING" in the main distribution directory for copyright.
#include "Tag.h"
#include "Manager.h"
using namespace file_analysis;
file_analysis::Tag file_analysis::Tag::Error;
file_analysis::Tag::Tag(type_t type, subtype_t subtype)
: ::Tag(file_mgr->GetTagEnumType(), type, subtype)
{
}
file_analysis::Tag& file_analysis::Tag::operator=(const file_analysis::Tag& other)
{
::Tag::operator=(other);
return *this;
}
EnumVal* file_analysis::Tag::AsEnumVal() const
{
return ::Tag::AsEnumVal(file_mgr->GetTagEnumType());
}

116
src/file_analysis/Tag.h Normal file
View file

@ -0,0 +1,116 @@
// See the file "COPYING" in the main distribution directory for copyright.
#ifndef FILE_ANALYZER_TAG_H
#define FILE_ANALYZER_TAG_H
#include "config.h"
#include "util.h"
#include "../Tag.h"
#include "plugin/TaggedComponent.h"
#include "plugin/ComponentManager.h"
class EnumVal;
namespace file_analysis {
class Component;
/**
* Class to identify a file analyzer type.
*
* The script-layer analogue is Files::Tag.
*/
class Tag : public ::Tag {
public:
/*
* Copy constructor.
*/
Tag(const Tag& other) : ::Tag(other) {}
/**
* Default constructor. This initializes the tag with an error value
* that will make \c operator \c bool return false.
*/
Tag() : ::Tag() {}
/**
* Destructor.
*/
~Tag() {}
/**
* Returns false if the tag represents an error value rather than a
* legal analyzer type.
* TODO: make this conversion operator "explicit" (C++11) or use a
* "safe bool" idiom (not necessary if "explicit" is available),
* otherwise this may allow nonsense/undesired comparison operations.
*
*/
operator bool() const { return *this != Tag(); }
/**
* Assignment operator.
*/
Tag& operator=(const Tag& other);
/**
* Compares two tags for equality.
*/
bool operator==(const Tag& other) const
{
return ::Tag::operator==(other);
}
/**
* Compares two tags for inequality.
*/
bool operator!=(const Tag& other) const
{
return ::Tag::operator!=(other);
}
/**
* Compares two tags for less-than relationship.
*/
bool operator<(const Tag& other) const
{
return ::Tag::operator<(other);
}
/**
* Returns the \c Files::Tag enum that corresponds to this tag.
* The returned value does not have its ref-count increased.
*
* @param etype the script-layer enum type associated with the tag.
*/
EnumVal* AsEnumVal() const;
static Tag Error;
protected:
friend class plugin::ComponentManager<Tag, Component>;
friend class plugin::TaggedComponent<Tag>;
/**
* Constructor.
*
* @param type The main type. Note that the \a file_analysis::Manager
* manages the value space internally, so noone else should assign
* main types.
*
* @param subtype The sub type, which is left to an analyzer for
* interpretation. By default it's set to zero.
*/
Tag(type_t type, subtype_t subtype = 0);
/**
* Constructor.
*
* @param val An enum value of script type \c Files::Tag.
*/
Tag(EnumVal* val) : ::Tag(val) {}
};
}
#endif

View file

@ -2,3 +2,5 @@ add_subdirectory(data_event)
add_subdirectory(extract)
add_subdirectory(hash)
add_subdirectory(pe)
add_subdirectory(unified2)
add_subdirectory(x509)

View file

@ -4,5 +4,5 @@ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR})
bro_plugin_begin(Bro FileDataEvent)
bro_plugin_cc(DataEvent.cc Plugin.cc)
bro_plugin_cc(DataEvent.cc Plugin.cc ../../Analyzer.cc)
bro_plugin_end()

View file

@ -6,24 +6,22 @@
#include "EventRegistry.h"
#include "Event.h"
#include "util.h"
#include "file_analysis/Manager.h"
using namespace file_analysis;
DataEvent::DataEvent(RecordVal* args, File* file,
EventHandlerPtr ce, EventHandlerPtr se)
: file_analysis::Analyzer(args, file), chunk_event(ce), stream_event(se)
: file_analysis::Analyzer(file_mgr->GetComponentTag("DATA_EVENT"),
args, file),
chunk_event(ce), stream_event(se)
{
}
file_analysis::Analyzer* DataEvent::Instantiate(RecordVal* args, File* file)
{
using BifType::Record::FileAnalysis::AnalyzerArgs;
int chunk_off = AnalyzerArgs->FieldOffset("chunk_event");
int stream_off = AnalyzerArgs->FieldOffset("stream_event");
Val* chunk_val = args->Lookup(chunk_off);
Val* stream_val = args->Lookup(stream_off);
Val* chunk_val = args->Lookup("chunk_event");
Val* stream_val = args->Lookup("stream_event");
if ( ! chunk_val && ! stream_val ) return 0;

View file

@ -1,26 +1,8 @@
#include "plugin/Plugin.h"
#include "file_analysis/Component.h"
#include "DataEvent.h"
namespace plugin { namespace Bro_FileDataEvent {
class Plugin : public plugin::Plugin {
protected:
void InitPreScript()
{
SetName("Bro::FileDataEvent");
SetVersion(-1);
SetAPIVersion(BRO_PLUGIN_API_VERSION);
SetDynamicPlugin(false);
SetDescription("Delivers file content via events");
AddComponent(new ::file_analysis::Component("DATA_EVENT",
::file_analysis::DataEvent::Instantiate));
}
};
Plugin __plugin;
} }
BRO_PLUGIN_BEGIN(Bro, FileDataEvent)
BRO_PLUGIN_DESCRIPTION("Delivers file content via events");
BRO_PLUGIN_FILE_ANALYZER("DATA_EVENT", DataEvent);
BRO_PLUGIN_END

View file

@ -4,5 +4,7 @@ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR})
bro_plugin_begin(Bro FileExtract)
bro_plugin_cc(Extract.cc Plugin.cc)
bro_plugin_cc(Extract.cc Plugin.cc ../../Analyzer.cc)
bro_plugin_bif(events.bif)
bro_plugin_bif(functions.bif)
bro_plugin_end()

View file

@ -4,11 +4,15 @@
#include "Extract.h"
#include "util.h"
#include "Event.h"
#include "file_analysis/Manager.h"
using namespace file_analysis;
Extract::Extract(RecordVal* args, File* file, const string& arg_filename)
: file_analysis::Analyzer(args, file), filename(arg_filename)
Extract::Extract(RecordVal* args, File* file, const string& arg_filename,
uint64 arg_limit)
: file_analysis::Analyzer(file_mgr->GetComponentTag("EXTRACT"), args, file),
filename(arg_filename), limit(arg_limit)
{
fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
@ -27,15 +31,50 @@ Extract::~Extract()
safe_close(fd);
}
static Val* get_extract_field_val(RecordVal* args, const char* name)
{
Val* rval = args->Lookup(name);
if ( ! rval )
reporter->Error("File extraction analyzer missing arg field: %s", name);
return rval;
}
file_analysis::Analyzer* Extract::Instantiate(RecordVal* args, File* file)
{
using BifType::Record::FileAnalysis::AnalyzerArgs;
Val* v = args->Lookup(AnalyzerArgs->FieldOffset("extract_filename"));
Val* fname = get_extract_field_val(args, "extract_filename");
Val* limit = get_extract_field_val(args, "extract_limit");
if ( ! v )
if ( ! fname || ! limit )
return 0;
return new Extract(args, file, v->AsString()->CheckString());
return new Extract(args, file, fname->AsString()->CheckString(),
limit->AsCount());
}
static bool check_limit_exceeded(uint64 lim, uint64 off, uint64 len, uint64* n)
{
if ( lim == 0 )
{
*n = len;
return false;
}
if ( off >= lim )
{
*n = 0;
return true;
}
*n = lim - off;
if ( len > *n )
return true;
else
*n = len;
return false;
}
bool Extract::DeliverChunk(const u_char* data, uint64 len, uint64 offset)
@ -43,6 +82,26 @@ bool Extract::DeliverChunk(const u_char* data, uint64 len, uint64 offset)
if ( ! fd )
return false;
safe_pwrite(fd, data, len, offset);
return true;
uint64 towrite = 0;
bool limit_exceeded = check_limit_exceeded(limit, offset, len, &towrite);
if ( limit_exceeded && file_extraction_limit )
{
File* f = GetFile();
val_list* vl = new val_list();
vl->append(f->GetVal()->Ref());
vl->append(Args()->Ref());
vl->append(new Val(limit, TYPE_COUNT));
vl->append(new Val(offset, TYPE_COUNT));
vl->append(new Val(len, TYPE_COUNT));
f->FileEvent(file_extraction_limit, vl);
// Limit may have been modified by BIF, re-check it.
limit_exceeded = check_limit_exceeded(limit, offset, len, &towrite);
}
if ( towrite > 0 )
safe_pwrite(fd, data, towrite, offset);
return ( ! limit_exceeded );
}

View file

@ -9,6 +9,8 @@
#include "File.h"
#include "Analyzer.h"
#include "analyzer/extract/events.bif.h"
namespace file_analysis {
/**
@ -41,6 +43,13 @@ public:
*/
static file_analysis::Analyzer* Instantiate(RecordVal* args, File* file);
/**
* Sets the maximum allowed extracted file size. A value of zero means
* "no limit".
* @param bytes number of bytes allowed to be extracted
*/
void SetLimit(uint64 bytes) { limit = bytes; }
protected:
/**
@ -49,12 +58,15 @@ protected:
* @param file the file to which the analyzer will be attached.
* @param arg_filename a file system path which specifies the local file
* to which the contents of the file will be extracted/written.
* @param arg_limit the maximum allowed file size.
*/
Extract(RecordVal* args, File* file, const string& arg_filename);
Extract(RecordVal* args, File* file, const string& arg_filename,
uint64 arg_limit);
private:
string filename;
int fd;
uint64 limit;
};
} // namespace file_analysis

View file

@ -1,26 +1,10 @@
#include "plugin/Plugin.h"
#include "file_analysis/Component.h"
#include "Extract.h"
namespace plugin { namespace Bro_FileExtract {
class Plugin : public plugin::Plugin {
protected:
void InitPreScript()
{
SetName("Bro::FileExtract");
SetVersion(-1);
SetAPIVersion(BRO_PLUGIN_API_VERSION);
SetDynamicPlugin(false);
SetDescription("Extract file content to local file system");
AddComponent(new ::file_analysis::Component("EXTRACT",
::file_analysis::Extract::Instantiate));
}
};
Plugin __plugin;
} }
BRO_PLUGIN_BEGIN(Bro, FileExtract)
BRO_PLUGIN_DESCRIPTION("Extract file content to local file system");
BRO_PLUGIN_FILE_ANALYZER("EXTRACT", Extract);
BRO_PLUGIN_BIF_FILE(events);
BRO_PLUGIN_BIF_FILE(functions);
BRO_PLUGIN_END

View file

@ -0,0 +1,19 @@
## This event is generated when a file extraction analyzer is about
## to exceed the maximum permitted file size allowed by the
## *extract_limit* field of :bro:see:`Files::AnalyzerArgs`.
## The analyzer is automatically removed from file *f*.
##
## f: The file.
##
## args: Arguments that identify a particular file extraction analyzer.
## This is only provided to be able to pass along to
## :bro:see:`FileExtract::set_limit`.
##
## limit: The limit, in bytes, the extracted file is about to breach.
##
## offset: The offset at which a file chunk is about to be written.
##
## len: The length of the file chunk about to be written.
##
## .. bro:see:: Files::add_analyzer Files::ANALYZER_EXTRACT
event file_extraction_limit%(f: fa_file, args: any, limit: count, offset: count, len: count%);

View file

@ -0,0 +1,19 @@
##! Internal functions used by the extraction file analyzer.
module FileExtract;
%%{
#include "file_analysis/Manager.h"
%%}
## :bro:see:`FileExtract::set_limit`.
function FileExtract::__set_limit%(file_id: string, args: any, n: count%): bool
%{
using BifType::Record::Files::AnalyzerArgs;
RecordVal* rv = args->AsRecordVal()->CoerceTo(AnalyzerArgs);
bool result = file_mgr->SetExtractionLimit(file_id->CheckString(), rv, n);
Unref(rv);
return new Val(result, TYPE_BOOL);
%}
module GLOBAL;

View file

@ -4,6 +4,6 @@ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR})
bro_plugin_begin(Bro FileHash)
bro_plugin_cc(Hash.cc Plugin.cc)
bro_plugin_cc(Hash.cc Plugin.cc ../../Analyzer.cc)
bro_plugin_bif(events.bif)
bro_plugin_end()

View file

@ -5,11 +5,12 @@
#include "Hash.h"
#include "util.h"
#include "Event.h"
#include "file_analysis/Manager.h"
using namespace file_analysis;
Hash::Hash(RecordVal* args, File* file, HashVal* hv, const char* arg_kind)
: file_analysis::Analyzer(args, file), hash(hv), fed(false), kind(arg_kind)
: file_analysis::Analyzer(file_mgr->GetComponentTag(to_upper(arg_kind).c_str()), args, file), hash(hv), fed(false), kind(arg_kind)
{
hash->Init();
}

View file

@ -1,33 +1,11 @@
#include "plugin/Plugin.h"
#include "file_analysis/Component.h"
#include "Hash.h"
namespace plugin { namespace Bro_FileHash {
class Plugin : public plugin::Plugin {
protected:
void InitPreScript()
{
SetName("Bro::FileHash");
SetVersion(-1);
SetAPIVersion(BRO_PLUGIN_API_VERSION);
SetDynamicPlugin(false);
SetDescription("Hash file content");
AddComponent(new ::file_analysis::Component("MD5",
::file_analysis::MD5::Instantiate));
AddComponent(new ::file_analysis::Component("SHA1",
::file_analysis::SHA1::Instantiate));
AddComponent(new ::file_analysis::Component("SHA256",
::file_analysis::SHA256::Instantiate));
extern std::list<std::pair<const char*, int> > __bif_events_init();
AddBifInitFunction(&__bif_events_init);
}
};
Plugin __plugin;
} }
BRO_PLUGIN_BEGIN(Bro, FileHash)
BRO_PLUGIN_DESCRIPTION("Hash file content");
BRO_PLUGIN_FILE_ANALYZER("MD5", MD5);
BRO_PLUGIN_FILE_ANALYZER("SHA1", SHA1);
BRO_PLUGIN_FILE_ANALYZER("SHA256", SHA256);
BRO_PLUGIN_BIF_FILE(events);
BRO_PLUGIN_END

View file

@ -7,6 +7,6 @@
##
## hash: The result of the hashing.
##
## .. bro:see:: FileAnalysis::add_analyzer FileAnalysis::ANALYZER_MD5
## FileAnalysis::ANALYZER_SHA1 FileAnalysis::ANALYZER_SHA256
## .. bro:see:: Files::add_analyzer Files::ANALYZER_MD5
## Files::ANALYZER_SHA1 Files::ANALYZER_SHA256
event file_hash%(f: fa_file, kind: string, hash: string%);

View file

@ -0,0 +1,11 @@
include(BroPlugin)
include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR})
bro_plugin_begin(Bro Unified2)
bro_plugin_cc(Unified2.cc Plugin.cc ../../Analyzer.cc)
bro_plugin_bif(events.bif types.bif)
bro_plugin_pac(unified2.pac unified2-file.pac unified2-analyzer.pac)
bro_plugin_end()

View file

@ -0,0 +1,12 @@
// See the file "COPYING" in the main distribution directory for copyright.
#include "plugin/Plugin.h"
#include "Unified2.h"
BRO_PLUGIN_BEGIN(Bro, Unified2)
BRO_PLUGIN_DESCRIPTION("Analyze Unified2 alert files.");
BRO_PLUGIN_FILE_ANALYZER("UNIFIED2", Unified2);
BRO_PLUGIN_BIF_FILE(events);
BRO_PLUGIN_BIF_FILE(types);
BRO_PLUGIN_END

View file

@ -0,0 +1,38 @@
// See the file "COPYING" in the main distribution directory for copyright.
#include "Unified2.h"
#include "file_analysis/Manager.h"
using namespace file_analysis;
Unified2::Unified2(RecordVal* args, File* file)
: file_analysis::Analyzer(file_mgr->GetComponentTag("UNIFIED2"), args, file)
{
interp = new binpac::Unified2::Unified2_Analyzer(this);
}
Unified2::~Unified2()
{
delete interp;
}
file_analysis::Analyzer* Unified2::Instantiate(RecordVal* args, File* file)
{
return new Unified2(args, file);
}
bool Unified2::DeliverStream(const u_char* data, uint64 len)
{
try
{
interp->NewData(true, data, data + len);
}
catch ( const binpac::Exception& e )
{
printf("Binpac exception: %s\n", e.c_msg());
return false;
}
return true;
}

View file

@ -0,0 +1,37 @@
// See the file "COPYING" in the main distribution directory for copyright.
#ifndef FILE_ANALYSIS_UNIFIED2_H
#define FILE_ANALYSIS_UNIFIED2_H
#include <string>
#include "Val.h"
#include "File.h"
#include "Analyzer.h"
#include "unified2_pac.h"
namespace file_analysis {
/**
* An analyzer to extract content of files from local disk.
*/
class Unified2 : public file_analysis::Analyzer {
public:
virtual ~Unified2();
virtual bool DeliverStream(const u_char* data, uint64 len);
static file_analysis::Analyzer* Instantiate(RecordVal* args, File* file);
protected:
Unified2(RecordVal* args, File* file);
private:
binpac::Unified2::Unified2_Analyzer* interp;
string filename;
};
} // namespace file_analysis
#endif

View file

@ -0,0 +1,17 @@
## Abstract all of the various Unified2 event formats into
## a single event.
##
## f: The file.
##
## ev: TODO.
##
event unified2_event%(f: fa_file, ev: Unified2::IDSEvent%);
## The Unified2 packet format event.
##
## f: The file.
##
## pkt: TODO.
##
event unified2_packet%(f: fa_file, pkt: Unified2::Packet%);

View file

@ -0,0 +1,2 @@
type Unified2::IDSEvent: record;
type Unified2::Packet: record;

View file

@ -0,0 +1,170 @@
%extern{
#include "Event.h"
#include "file_analysis/File.h"
#include "events.bif.h"
#include "types.bif.h"
#include "IPAddr.h"
%}
refine flow Flow += {
%member{
%}
%init{
%}
%eof{
%}
%cleanup{
%}
function ts_to_double(ts: Time): double
%{
double t = ${ts.seconds} + (${ts.microseconds} / 1000000);
return t;
%}
function unified2_addr_to_bro_addr(a: uint32[]): AddrVal
%{
if ( a->size() == 1 )
{
return new AddrVal(IPAddr(IPv4, &(a->at(0)), IPAddr::Host));
}
else if ( a->size() == 4 )
{
uint32 tmp[4] = { a->at(0), a->at(1), a->at(2), a->at(3) };
return new AddrVal(IPAddr(IPv6, tmp, IPAddr::Host));
}
else
{
// Should never reach here.
return new AddrVal(1);
}
%}
function to_port(n: uint16, p: uint8): PortVal
%{
TransportProto proto = TRANSPORT_UNKNOWN;
switch ( p ) {
case 1: proto = TRANSPORT_ICMP; break;
case 6: proto = TRANSPORT_TCP; break;
case 17: proto = TRANSPORT_UDP; break;
}
return new PortVal(n, proto);
%}
#function proc_record(rec: Record) : bool
# %{
# return true;
# %}
function proc_ids_event(ev: IDS_Event) : bool
%{
if ( ::unified2_event )
{
RecordVal* ids_event = new RecordVal(BifType::Record::Unified2::IDSEvent);
ids_event->Assign(0, new Val(${ev.sensor_id}, TYPE_COUNT));
ids_event->Assign(1, new Val(${ev.event_id}, TYPE_COUNT));
ids_event->Assign(2, new Val(ts_to_double(${ev.ts}), TYPE_TIME));
ids_event->Assign(3, new Val(${ev.signature_id}, TYPE_COUNT));
ids_event->Assign(4, new Val(${ev.generator_id}, TYPE_COUNT));
ids_event->Assign(5, new Val(${ev.signature_revision}, TYPE_COUNT));
ids_event->Assign(6, new Val(${ev.classification_id}, TYPE_COUNT));
ids_event->Assign(7, new Val(${ev.priority_id}, TYPE_COUNT));
ids_event->Assign(8, unified2_addr_to_bro_addr(${ev.src_ip}));
ids_event->Assign(9, unified2_addr_to_bro_addr(${ev.dst_ip}));
ids_event->Assign(10, to_port(${ev.src_p}, ${ev.protocol}));
ids_event->Assign(11, to_port(${ev.dst_p}, ${ev.protocol}));
ids_event->Assign(17, new Val(${ev.packet_action}, TYPE_COUNT));
val_list* vl = new val_list();
vl->append(connection()->bro_analyzer()->GetFile()->GetVal()->Ref());
vl->append(ids_event);
mgr.QueueEvent(::unified2_event, vl, SOURCE_LOCAL);
}
return true;
%}
function proc_ids_event_2(ev: IDS_Event_2) : bool
%{
if ( ::unified2_event )
{
RecordVal* ids_event = new RecordVal(BifType::Record::Unified2::IDSEvent);
ids_event->Assign(0, new Val(${ev.sensor_id}, TYPE_COUNT));
ids_event->Assign(1, new Val(${ev.event_id}, TYPE_COUNT));
ids_event->Assign(2, new Val(ts_to_double(${ev.ts}), TYPE_TIME));
ids_event->Assign(3, new Val(${ev.signature_id}, TYPE_COUNT));
ids_event->Assign(4, new Val(${ev.generator_id}, TYPE_COUNT));
ids_event->Assign(5, new Val(${ev.signature_revision}, TYPE_COUNT));
ids_event->Assign(6, new Val(${ev.classification_id}, TYPE_COUNT));
ids_event->Assign(7, new Val(${ev.priority_id}, TYPE_COUNT));
ids_event->Assign(8, unified2_addr_to_bro_addr(${ev.src_ip}));
ids_event->Assign(9, unified2_addr_to_bro_addr(${ev.dst_ip}));
ids_event->Assign(10, to_port(${ev.src_p}, ${ev.protocol}));
ids_event->Assign(11, to_port(${ev.dst_p}, ${ev.protocol}));
ids_event->Assign(12, new Val(${ev.impact_flag}, TYPE_COUNT));
ids_event->Assign(13, new Val(${ev.impact}, TYPE_COUNT));
ids_event->Assign(14, new Val(${ev.blocked}, TYPE_COUNT));
ids_event->Assign(15, new Val(${ev.mpls_label}, TYPE_COUNT));
ids_event->Assign(16, new Val(${ev.vlan_id}, TYPE_COUNT));
val_list* vl = new val_list();
vl->append(connection()->bro_analyzer()->GetFile()->GetVal()->Ref());
vl->append(ids_event);
mgr.QueueEvent(::unified2_event, vl, SOURCE_LOCAL);
}
return true;
%}
function proc_packet(pkt: Packet) : bool
%{
if ( ::unified2_packet )
{
RecordVal* packet = new RecordVal(BifType::Record::Unified2::Packet);
packet->Assign(0, new Val(${pkt.sensor_id}, TYPE_COUNT));
packet->Assign(1, new Val(${pkt.event_id}, TYPE_COUNT));
packet->Assign(2, new Val(${pkt.event_second}, TYPE_COUNT));
packet->Assign(3, new Val(ts_to_double(${pkt.packet_ts}), TYPE_TIME));
packet->Assign(4, new Val(${pkt.link_type}, TYPE_COUNT));
packet->Assign(5, bytestring_to_val(${pkt.packet_data}));
val_list* vl = new val_list();
vl->append(connection()->bro_analyzer()->GetFile()->GetVal()->Ref());
vl->append(packet);
mgr.QueueEvent(::unified2_packet, vl, SOURCE_LOCAL);
}
return true;
%}
#function proc_unknown_record_type(rec: UnknownRecordType) : bool
# %{
# printf("unknown packet type\n");
# return true;
# %}
};
#refine typeattr Record += &let {
# proc : bool = $context.flow.proc_record(this);
#};
refine typeattr IDS_Event += &let {
proc : bool = $context.flow.proc_ids_event(this);
};
refine typeattr IDS_Event_2 += &let {
proc : bool = $context.flow.proc_ids_event_2(this);
};
refine typeattr Packet += &let {
proc : bool = $context.flow.proc_packet(this);
};
#refine typeattr UnknownRecordType += &let {
# proc : bool = $context.flow.proc_unknown_record_type(this);
#};

View file

@ -0,0 +1,91 @@
enum Types {
PACKET = 2,
IDS_EVENT = 7,
IDS_EVENT_IPV6 = 72,
IDS_EVENT_2 = 104,
IDS_EVENT_IPV6_2 = 105,
EXTRA_DATA = 110,
};
type Time = record {
seconds: uint32;
microseconds: uint32;
} &byteorder=bigendian;
type Record = record {
rtype: uint32;
length: uint32;
data: case rtype of {
PACKET -> packet: Packet(this);
IDS_EVENT -> ids_event: IDS_Event(this, 1);
IDS_EVENT_IPV6 -> ids_event_ipv6: IDS_Event(this, 4);
IDS_EVENT_2 -> ids_event_vlan: IDS_Event_2(this, 1);
IDS_EVENT_IPV6_2 -> ids_event_ipv6_vlan: IDS_Event_2(this, 4);
#EXTRA_DATA -> extra_data: ExtraData(this);
default -> unknown_record_type: UnknownRecordType(this);
};
} &byteorder=bigendian &length=length+8;
type IDS_Event(rec: Record, ip_len: int) = record {
sensor_id: uint32;
event_id: uint32;
ts: Time;
signature_id: uint32;
generator_id: uint32;
signature_revision: uint32;
classification_id: uint32;
priority_id: uint32;
src_ip: uint32[ip_len];
dst_ip: uint32[ip_len];
src_p: uint16;
dst_p: uint16;
protocol: uint8;
packet_action: uint8;
} &byteorder=bigendian;
type IDS_Event_2(rec: Record, ip_len: int) = record {
sensor_id: uint32;
event_id: uint32;
ts: Time;
signature_id: uint32;
generator_id: uint32;
signature_revision: uint32;
classification_id: uint32;
priority_id: uint32;
src_ip: uint32[ip_len];
dst_ip: uint32[ip_len];
src_p: uint16;
dst_p: uint16;
protocol: uint8;
impact_flag: uint8;
impact: uint8;
blocked: uint8;
mpls_label: uint32;
vlan_id: uint16;
pad: uint16;
} &byteorder=bigendian;
type Packet(rec: Record) = record {
sensor_id: uint32;
event_id: uint32;
event_second: uint32;
packet_ts: Time;
link_type: uint32;
packet_len: uint32;
packet_data: bytestring &length=packet_len;
} &byteorder=bigendian;
type ExtraData(rec: Record) = record {
sensor_id: uint32;
event_id: uint32;
event_second: uint32;
extra_type: uint32;
data_type: uint32;
blob_len: uint32;
blob: bytestring &length=blob_len;
} &byteorder=bigendian &length=rec.length;
type UnknownRecordType(rec: Record) = record {
data: bytestring &transient &length=rec.length;
} &byteorder=bigendian &length=rec.length;

View file

@ -0,0 +1,21 @@
%include binpac.pac
%include bro.pac
analyzer Unified2 withcontext {
analyzer: Unified2_Analyzer;
flow: Flow;
};
analyzer Unified2_Analyzer(bro_analyzer: BroFileAnalyzer) {
downflow = Flow;
upflow = Flow;
};
%include unified2-file.pac
flow Flow {
flowunit = Record withcontext(connection, this);
};
%include unified2-analyzer.pac

View file

@ -0,0 +1,10 @@
include(BroPlugin)
include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR})
bro_plugin_begin(Bro X509)
bro_plugin_cc(X509.cc Plugin.cc)
bro_plugin_bif(events.bif types.bif functions.bif)
bro_plugin_end()

View file

@ -0,0 +1,11 @@
#include "plugin/Plugin.h"
#include "X509.h"
BRO_PLUGIN_BEGIN(Bro, X509)
BRO_PLUGIN_DESCRIPTION("X509 certificate parser");
BRO_PLUGIN_FILE_ANALYZER("X509", X509);
BRO_PLUGIN_BIF_FILE(events);
BRO_PLUGIN_BIF_FILE(types);
BRO_PLUGIN_BIF_FILE(functions);
BRO_PLUGIN_END

View file

@ -0,0 +1,635 @@
// See the file "COPYING" in the main distribution directory for copyright.
#include <string>
#include "X509.h"
#include "Event.h"
#include "events.bif.h"
#include "types.bif.h"
#include "file_analysis/Manager.h"
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
#include <openssl/opensslconf.h>
using namespace file_analysis;
IMPLEMENT_SERIAL(X509Val, SER_X509_VAL);
file_analysis::X509::X509(RecordVal* args, file_analysis::File* file)
: file_analysis::Analyzer(file_mgr->GetComponentTag("X509"), args, file)
{
cert_data.clear();
}
bool file_analysis::X509::DeliverStream(const u_char* data, uint64 len)
{
// just add it to the data we have so far, since we cannot do anything else anyways...
cert_data.append(reinterpret_cast<const char*>(data), len);
return true;
}
bool file_analysis::X509::Undelivered(uint64 offset, uint64 len)
{
return false;
}
bool file_analysis::X509::EndOfFile()
{
// ok, now we can try to parse the certificate with openssl. Should
// be rather straightforward...
const unsigned char* cert_char = reinterpret_cast<const unsigned char*>(cert_data.data());
::X509* ssl_cert = d2i_X509(NULL, &cert_char, cert_data.size());
if ( ! ssl_cert )
{
reporter->Weird(fmt("Could not parse X509 certificate (fuid %s)", GetFile()->GetID().c_str()));
return false;
}
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
// and send the record on to scriptland
val_list* vl = new val_list();
vl->append(GetFile()->GetVal()->Ref());
vl->append(cert_val->Ref());
vl->append(cert_record->Ref()); // we Ref it here, because we want to keep a copy around for now...
mgr.QueueEvent(x509_certificate, vl);
// after parsing the certificate - parse the extensions...
int num_ext = X509_get_ext_count(ssl_cert);
for ( int k = 0; k < num_ext; ++k )
{
X509_EXTENSION* ex = X509_get_ext(ssl_cert, k);
if ( ! ex )
continue;
ParseExtension(ex);
}
// X509_free(ssl_cert); We do _not_ free the certificate here. It is refcounted
// inside the X509Val that is sent on in the cert record to scriptland.
//
// The certificate will be freed when the last X509Val is Unref'd.
Unref(cert_record); // Unref the RecordVal that we kept around from ParseCertificate
Unref(cert_val); // Same for cert_val
return false;
}
RecordVal* file_analysis::X509::ParseCertificate(X509Val* cert_val)
{
::X509* ssl_cert = cert_val->GetCertificate();
char buf[2048]; // we need a buffer for some of the openssl functions
memset(buf, 0, sizeof(buf));
RecordVal* pX509Cert = new RecordVal(BifType::Record::X509::Certificate);
BIO *bio = BIO_new(BIO_s_mem());
pX509Cert->Assign(0, new Val((uint64) X509_get_version(ssl_cert) + 1, TYPE_COUNT));
i2a_ASN1_INTEGER(bio, X509_get_serialNumber(ssl_cert));
int len = BIO_read(bio, buf, sizeof(buf));
pX509Cert->Assign(1, new StringVal(len, buf));
BIO_reset(bio);
X509_NAME_print_ex(bio, X509_get_subject_name(ssl_cert), 0, XN_FLAG_RFC2253);
len = BIO_gets(bio, buf, sizeof(buf));
pX509Cert->Assign(2, 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));
// we only read 255 bytes because byte 256 is always 0.
// if the string is longer than 255, that will be our null-termination,
// otherwhise i2t does null-terminate.
if ( ! i2t_ASN1_OBJECT(buf, 255, ssl_cert->cert_info->key->algor->algorithm) )
buf[0] = 0;
pX509Cert->Assign(6, new StringVal(buf));
if ( ! i2t_ASN1_OBJECT(buf, 255, ssl_cert->sig_alg->algorithm) )
buf[0] = 0;
pX509Cert->Assign(7, 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"));
else if ( pkey->type == EVP_PKEY_RSA )
{
pX509Cert->Assign(8, new StringVal("rsa"));
char *exponent = BN_bn2dec(pkey->pkey.rsa->e);
if ( exponent != NULL )
{
pX509Cert->Assign(10, new StringVal(exponent));
OPENSSL_free(exponent);
exponent = NULL;
}
}
#ifndef OPENSSL_NO_EC
else if ( pkey->type == EVP_PKEY_EC )
{
pX509Cert->Assign(8, new StringVal("dsa"));
pX509Cert->Assign(11, KeyCurve(pkey));
}
#endif
unsigned int length = KeyLength(pkey);
if ( length > 0 )
pX509Cert->Assign(9, new Val(length, TYPE_COUNT));
EVP_PKEY_free(pkey);
}
return pX509Cert;
}
StringVal* file_analysis::X509::GetExtensionFromBIO(BIO* bio)
{
BIO_flush(bio);
ERR_clear_error();
int length = BIO_pending(bio);
if ( ERR_peek_error() != 0 )
{
char tmp[120];
ERR_error_string_n(ERR_get_error(), tmp, sizeof(tmp));
reporter->Weird(fmt("X509::GetExtensionFromBIO: %s", tmp));
BIO_free_all(bio);
return 0;
}
if ( length == 0 )
{
BIO_free_all(bio);
return new StringVal("");
}
char* buffer = (char*) malloc(length);
if ( ! buffer )
{
// Just emit an error here and try to continue instead of aborting
// because it's unclear the length value is very reliable.
reporter->Error("X509::GetExtensionFromBIO malloc(%d) failed", length);
BIO_free_all(bio);
return 0;
}
BIO_read(bio, (void*) buffer, length);
StringVal* ext_val = new StringVal(length, buffer);
free(buffer);
BIO_free_all(bio);
return ext_val;
}
void file_analysis::X509::ParseExtension(X509_EXTENSION* ex)
{
char name[256];
char oid[256];
ASN1_OBJECT* ext_asn = X509_EXTENSION_get_object(ex);
const char* short_name = OBJ_nid2sn(OBJ_obj2nid(ext_asn));
OBJ_obj2txt(name, 255, ext_asn, 0);
OBJ_obj2txt(oid, 255, ext_asn, 1);
int critical = 0;
if ( X509_EXTENSION_get_critical(ex) != 0 )
critical = 1;
BIO *bio = BIO_new(BIO_s_mem());
if( ! X509V3_EXT_print(bio, ex, 0, 0))
M_ASN1_OCTET_STRING_print(bio,ex->value);
StringVal* ext_val = GetExtensionFromBIO(bio);
if ( ! ext_val )
ext_val = new StringVal(0, "");
RecordVal* pX509Ext = new RecordVal(BifType::Record::X509::Extension);
pX509Ext->Assign(0, new StringVal(name));
if ( short_name and strlen(short_name) > 0 )
pX509Ext->Assign(1, new StringVal(short_name));
pX509Ext->Assign(2, new StringVal(oid));
pX509Ext->Assign(3, new Val(critical, TYPE_BOOL));
pX509Ext->Assign(4, ext_val);
// send off generic extension event
//
// and then look if we have a specialized event for the extension we just
// parsed. And if we have it, we send the specialized event on top of the
// generic event that we just had. I know, that is... kind of not nice,
// but I am not sure if there is a better way to do it...
val_list* vl = new val_list();
vl->append(GetFile()->GetVal()->Ref());
vl->append(pX509Ext);
mgr.QueueEvent(x509_extension, vl);
// look if we have a specialized handler for this event...
if ( OBJ_obj2nid(ext_asn) == NID_basic_constraints )
ParseBasicConstraints(ex);
else if ( OBJ_obj2nid(ext_asn) == NID_subject_alt_name )
ParseSAN(ex);
}
void file_analysis::X509::ParseBasicConstraints(X509_EXTENSION* ex)
{
assert(OBJ_obj2nid(X509_EXTENSION_get_object(ex)) == NID_basic_constraints);
BASIC_CONSTRAINTS *constr = (BASIC_CONSTRAINTS *) X509V3_EXT_d2i(ex);
if ( constr )
{
RecordVal* pBasicConstraint = new RecordVal(BifType::Record::X509::BasicConstraints);
pBasicConstraint->Assign(0, new Val(constr->ca ? 1 : 0, TYPE_BOOL));
if ( constr->pathlen )
pBasicConstraint->Assign(1, new Val((int32_t) ASN1_INTEGER_get(constr->pathlen), TYPE_COUNT));
val_list* vl = new val_list();
vl->append(GetFile()->GetVal()->Ref());
vl->append(pBasicConstraint);
mgr.QueueEvent(x509_ext_basic_constraints, vl);
BASIC_CONSTRAINTS_free(constr);
}
else
reporter->Weird(fmt("Certificate with invalid BasicConstraint. fuid %s", GetFile()->GetID().c_str()));
}
void file_analysis::X509::ParseSAN(X509_EXTENSION* ext)
{
assert(OBJ_obj2nid(X509_EXTENSION_get_object(ext)) == NID_subject_alt_name);
GENERAL_NAMES *altname = (GENERAL_NAMES*)X509V3_EXT_d2i(ext);
if ( ! altname )
{
reporter->Weird(fmt("Could not parse subject alternative names. fuid %s", GetFile()->GetID().c_str()));
return;
}
VectorVal* names = 0;
VectorVal* emails = 0;
VectorVal* uris = 0;
VectorVal* ips = 0;
unsigned int otherfields = 0;
for ( int i = 0; i < sk_GENERAL_NAME_num(altname); i++ )
{
GENERAL_NAME *gen = sk_GENERAL_NAME_value(altname, i);
assert(gen);
if ( gen->type == GEN_DNS || gen->type == GEN_URI || gen->type == GEN_EMAIL )
{
if ( ASN1_STRING_type(gen->d.ia5) != V_ASN1_IA5STRING )
{
reporter->Weird(fmt("DNS-field does not contain an IA5String. fuid %s", GetFile()->GetID().c_str()));
continue;
}
const char* name = (const char*) ASN1_STRING_data(gen->d.ia5);
StringVal* bs = new StringVal(name);
switch ( gen->type )
{
case GEN_DNS:
if ( names == 0 )
names = new VectorVal(internal_type("string_vec")->AsVectorType());
names->Assign(names->Size(), bs);
break;
case GEN_URI:
if ( uris == 0 )
uris = new VectorVal(internal_type("string_vec")->AsVectorType());
uris->Assign(uris->Size(), bs);
break;
case GEN_EMAIL:
if ( emails == 0 )
emails = new VectorVal(internal_type("string_vec")->AsVectorType());
emails->Assign(emails->Size(), bs);
break;
}
}
else if ( gen->type == GEN_IPADD )
{
if ( ips == 0 )
ips = new VectorVal(internal_type("addr_vec")->AsVectorType());
uint32* addr = (uint32*) gen->d.ip->data;
if( gen->d.ip->length == 4 )
ips->Assign(ips->Size(), new AddrVal(*addr));
else if ( gen->d.ip->length == 16 )
ips->Assign(ips->Size(), new AddrVal(addr));
else
{
reporter->Weird(fmt("Weird IP address length %d in subject alternative name. fuid %s", gen->d.ip->length, GetFile()->GetID().c_str()));
continue;
}
}
else
{
// reporter->Error("Subject alternative name contained unsupported fields. fuid %s", GetFile()->GetID().c_str());
// This happens quite often - just mark it
otherfields = 1;
continue;
}
}
RecordVal* sanExt = new RecordVal(BifType::Record::X509::SubjectAlternativeName);
if ( names != 0 )
sanExt->Assign(0, names);
if ( uris != 0 )
sanExt->Assign(1, uris);
if ( emails != 0 )
sanExt->Assign(2, emails);
if ( ips != 0 )
sanExt->Assign(3, ips);
sanExt->Assign(4, new Val(otherfields, TYPE_BOOL));
val_list* vl = new val_list();
vl->append(GetFile()->GetVal()->Ref());
vl->append(sanExt);
mgr.QueueEvent(x509_ext_subject_alternative_name, vl);
GENERAL_NAMES_free(altname);
}
StringVal* file_analysis::X509::KeyCurve(EVP_PKEY *key)
{
assert(key != NULL);
#ifdef OPENSSL_NO_EC
// well, we do not have EC-Support...
return NULL;
#else
if ( key->type != EVP_PKEY_EC )
{
// no EC-key - no curve name
return NULL;
}
const EC_GROUP *group;
int nid;
if ( (group = EC_KEY_get0_group(key->pkey.ec)) == NULL)
// I guess we could not parse this
return NULL;
nid = EC_GROUP_get_curve_name(group);
if ( nid == 0 )
// and an invalid nid...
return NULL;
const char * curve_name = OBJ_nid2sn(nid);
if ( curve_name == NULL )
return NULL;
return new StringVal(curve_name);
#endif
}
unsigned int file_analysis::X509::KeyLength(EVP_PKEY *key)
{
assert(key != NULL);
switch(key->type) {
case EVP_PKEY_RSA:
return BN_num_bits(key->pkey.rsa->n);
case EVP_PKEY_DSA:
return BN_num_bits(key->pkey.dsa->p);
#ifndef OPENSSL_NO_EC
case EVP_PKEY_EC:
{
BIGNUM* ec_order = BN_new();
if ( ! ec_order )
// could not malloc bignum?
return 0;
const EC_GROUP *group = EC_KEY_get0_group(key->pkey.ec);
if ( ! group )
{
// unknown ex-group
BN_free(ec_order);
return 0;
}
if ( ! EC_GROUP_get_order(group, ec_order, NULL) )
{
// could not get ec-group-order
BN_free(ec_order);
return 0;
}
unsigned int length = BN_num_bits(ec_order);
BN_free(ec_order);
return length;
}
#endif
default:
return 0; // unknown public key type
}
reporter->InternalError("cannot be reached");
}
double file_analysis::X509::GetTimeFromAsn1(const ASN1_TIME* atime)
{
time_t lResult = 0;
char lBuffer[24];
char* pBuffer = lBuffer;
size_t lTimeLength = atime->length;
char * pString = (char *) atime->data;
if ( atime->type == V_ASN1_UTCTIME )
{
if ( lTimeLength < 11 || lTimeLength > 17 )
return 0;
memcpy(pBuffer, pString, 10);
pBuffer += 10;
pString += 10;
}
else
{
if ( lTimeLength < 13 )
return 0;
memcpy(pBuffer, pString, 12);
pBuffer += 12;
pString += 12;
}
if ((*pString == 'Z') || (*pString == '-') || (*pString == '+'))
{
*(pBuffer++) = '0';
*(pBuffer++) = '0';
}
else
{
*(pBuffer++) = *(pString++);
*(pBuffer++) = *(pString++);
// Skip any fractional seconds...
if (*pString == '.')
{
pString++;
while ((*pString >= '0') && (*pString <= '9'))
pString++;
}
}
*(pBuffer++) = 'Z';
*(pBuffer++) = '\0';
time_t lSecondsFromUTC;
if ( *pString == 'Z' )
lSecondsFromUTC = 0;
else
{
if ((*pString != '+') && (pString[5] != '-'))
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');
if ( lTime.tm_year < 50 )
lTime.tm_year += 100; // RFC 2459
lTime.tm_wday = 0;
lTime.tm_yday = 0;
lTime.tm_isdst = 0; // No DST adjustment requested
lResult = mktime(&lTime);
if ( lResult )
{
if ( 0 != lTime.tm_isdst )
lResult -= 3600; // mktime may adjust for DST (OS dependent)
lResult += lSecondsFromUTC;
}
else
lResult = 0;
return lResult;
}
X509Val::X509Val(::X509* arg_certificate) : OpaqueVal(x509_opaque_type)
{
certificate = arg_certificate;
}
X509Val::X509Val() : OpaqueVal(x509_opaque_type)
{
certificate = 0;
}
X509Val::~X509Val()
{
if ( certificate )
X509_free(certificate);
}
::X509* X509Val::GetCertificate() const
{
return certificate;
}
bool X509Val::DoSerialize(SerialInfo* info) const
{
DO_SERIALIZE(SER_X509_VAL, OpaqueVal);
unsigned char *buf = NULL;
int length = i2d_X509(certificate, &buf);
if ( length < 0 )
return false;
bool res = SERIALIZE_STR(reinterpret_cast<const char*>(buf), length);
OPENSSL_free(buf);
return res;
}
bool X509Val::DoUnserialize(UnserialInfo* info)
{
DO_UNSERIALIZE(OpaqueVal)
int length;
unsigned char *certbuf, *opensslbuf;
if ( ! UNSERIALIZE_STR(reinterpret_cast<char **>(&certbuf), &length) )
return false;
opensslbuf = certbuf; // OpenSSL likes to shift pointers around. really.
certificate = d2i_X509(NULL, const_cast<const unsigned char**>(&opensslbuf), length);
delete[] certbuf;
if ( !certificate )
return false;
return true;
}

View file

@ -0,0 +1,112 @@
// See the file "COPYING" in the main distribution directory for copyright.
#ifndef FILE_ANALYSIS_X509_H
#define FILE_ANALYSIS_X509_H
#include <string>
#include "Val.h"
#include "../File.h"
#include "Analyzer.h"
#include <openssl/x509.h>
#include <openssl/asn1.h>
namespace file_analysis {
class X509Val;
class X509 : public file_analysis::Analyzer {
public:
virtual bool DeliverStream(const u_char* data, uint64 len);
virtual bool Undelivered(uint64 offset, uint64 len);
virtual bool EndOfFile();
/**
* Converts an X509 certificate into a \c X509::Certificate record
* value. This is a static function that can be called from external,
* it doesn't depend on the state of any particular file analyzer.
*
* @param cert_val The certificate to converts.
*
* @param Returns the new record value and passes ownership to
* caller.
*/
static RecordVal* ParseCertificate(X509Val* cert_val);
static file_analysis::Analyzer* Instantiate(RecordVal* args, File* file)
{ return new X509(args, file); }
/**
* Retrieve an X509 extension value from an OpenSSL BIO to which it was
* written.
*
* @param bio the OpenSSL BIO to read. It will be freed by the function,
* including when an error occurs.
*
* @return The X509 extension value.
*/
static StringVal* GetExtensionFromBIO(BIO* bio);
protected:
X509(RecordVal* args, File* file);
private:
void ParseExtension(X509_EXTENSION* ex);
void ParseBasicConstraints(X509_EXTENSION* ex);
void ParseSAN(X509_EXTENSION* ex);
std::string cert_data;
// Helpers for ParseCertificate.
static double GetTimeFromAsn1(const ASN1_TIME * atime);
static StringVal* KeyCurve(EVP_PKEY *key);
static unsigned int KeyLength(EVP_PKEY *key);
};
/**
* This class wraps an OpenSSL X509 data structure.
*
* We need these to be able to pass OpenSSL pointers around in Bro
* script-land. Otherwise, we cannot verify certificates from Bro
* scriptland
*/
class X509Val : public OpaqueVal {
public:
/**
* Construct an X509Val.
*
* @param certificate specifies the wrapped OpenSSL certificate
*
* @return A newly initialized X509Val.
*/
explicit X509Val(::X509* certificate);
/**
* Destructor.
*/
~X509Val();
/**
* Get the wrapped X509 certificate. Please take care, that the
* internal OpenSSL reference counting stays the same.
*
* @return The wrapped OpenSSL X509 certificate.
*/
::X509* GetCertificate() const;
protected:
/**
* Construct an empty X509Val. Only used for deserialization
*/
X509Val();
private:
::X509* certificate; // the wrapped certificate
DECLARE_SERIAL(X509Val);
};
}
#endif

View file

@ -0,0 +1,57 @@
## Generated for encountered X509 certificates, e.g., in the clear SSL/TLS
## connection handshake.
##
## See `Wikipedia <http://en.wikipedia.org/wiki/X.509>`__ for more information
## about the X.509 format.
##
## f: The file.
##
## cert_ref: An opaque pointer to the underlying OpenSSL data structure of the
## certificate.
##
## cert: The parsed certificate information.
##
## .. bro:see:: x509_extension x509_ext_basic_constraints
## x509_ext_subject_alternative_name x509_parse x509_verify
## x509_get_certificate_string
event x509_certificate%(f: fa_file, cert_ref: opaque of x509, cert: X509::Certificate%);
## Generated for X509 extensions seen in a certificate.
##
## See `Wikipedia <http://en.wikipedia.org/wiki/X.509>`__ for more information
## about the X.509 format.
##
## f: The file.
##
## ext: The parsed extension.
##
## .. bro:see:: x509_certificate x509_ext_basic_constraints
## x509_ext_subject_alternative_name x509_parse x509_verify
## x509_get_certificate_string
event x509_extension%(f: fa_file, ext: X509::Extension%);
## Generated for the X509 basic constraints extension seen in a certificate.
## This extension can be used to identify the subject of a certificate as a CA.
##
## f: The file.
##
## ext: The parsed basic constraints extension.
##
## .. bro:see:: x509_certificate x509_extension
## x509_ext_subject_alternative_name x509_parse x509_verify
## x509_get_certificate_string
event x509_ext_basic_constraints%(f: fa_file, ext: X509::BasicConstraints%);
## Generated for the X509 subject alternative name extension seen in a certificate.
## This extension can be used to allow additional entities to be bound to the
## subject of the certificate. Usually it is used to specify one or multiple DNS
## names for which a certificate is valid.
##
## f: The file.
##
## ext: The parsed subject alternative name extension.
##
## .. bro:see:: x509_certificate x509_extension x509_ext_basic_constraints
## x509_parse x509_verify
## x509_get_certificate_string
event x509_ext_subject_alternative_name%(f: fa_file, ext: X509::SubjectAlternativeName%);

View file

@ -0,0 +1,478 @@
%%{
#include "file_analysis/analyzer/x509/X509.h"
#include "types.bif.h"
#include <openssl/x509.h>
#include <openssl/asn1.h>
#include <openssl/x509_vfy.h>
#include <openssl/ocsp.h>
// This is the indexed map of X509 certificate stores.
static map<Val*, X509_STORE*> x509_stores;
// ### NOTE: while d2i_X509 does not take a const u_char** pointer,
// here we assume d2i_X509 does not write to <data>, so it is safe to
// convert data to a non-const pointer. Could some X509 guru verify
// this?
X509* d2i_X509_(X509** px, const u_char** in, int len)
{
#ifdef OPENSSL_D2I_X509_USES_CONST_CHAR
return d2i_X509(px, in, len);
#else
return d2i_X509(px, (u_char**)in, len);
#endif
}
// construct an error record
RecordVal* x509_result_record(uint64_t num, const char* reason, Val* chainVector = 0)
{
RecordVal* rrecord = new RecordVal(BifType::Record::X509::Result);
rrecord->Assign(0, new Val(num, TYPE_INT));
rrecord->Assign(1, new StringVal(reason));
if ( chainVector )
rrecord->Assign(2, chainVector);
return rrecord;
}
X509_STORE* x509_get_root_store(TableVal* root_certs)
{
// If this certificate store was built previously, just reuse the old one.
if ( x509_stores.count(root_certs) > 0 )
return x509_stores[root_certs];
X509_STORE* ctx = X509_STORE_new();
ListVal* idxs = root_certs->ConvertToPureList();
// Build the validation store
for ( int i = 0; i < idxs->Length(); ++i )
{
Val* key = idxs->Index(i);
StringVal *sv = root_certs->Lookup(key)->AsStringVal();
assert(sv);
const uint8* data = sv->Bytes();
X509* x = d2i_X509_(NULL, &data, sv->Len());
if ( ! x )
{
builtin_error(fmt("Root CA error: %s", ERR_error_string(ERR_get_error(),NULL)));
return 0;
}
X509_STORE_add_cert(ctx, x);
X509_free(x);
}
delete idxs;
// Save the newly constructed certificate store into the cacheing map.
x509_stores[root_certs] = ctx;
return ctx;
}
// get all cretificates starting at the second one (assuming the first one is the host certificate)
STACK_OF(X509)* x509_get_untrusted_stack(VectorVal* certs_vec)
{
STACK_OF(X509)* untrusted_certs = sk_X509_new_null();
if ( ! untrusted_certs )
{
builtin_error(fmt("Untrusted certificate stack initialization error: %s", ERR_error_string(ERR_get_error(),NULL)));
return 0;
}
for ( int i = 1; i < (int) certs_vec->Size(); ++i ) // start at 1 - 0 is host cert
{
Val *sv = certs_vec->Lookup(i);
if ( ! sv )
continue;
// Fixme: check type
X509* x = ((file_analysis::X509Val*) sv)->GetCertificate();
if ( ! x )
{
sk_X509_free(untrusted_certs);
builtin_error(fmt("No certificate in opaque in stack"));
return 0;
}
sk_X509_push(untrusted_certs, x);
}
return untrusted_certs;
}
%%}
## Parses a certificate into an X509::Certificate structure.
##
## cert: The X509 certificate opaque handle.
##
## Returns: A X509::Certificate structure.
##
## .. bro:see:: x509_certificate x509_extension x509_ext_basic_constraints
## x509_ext_subject_alternative_name x509_verify
## x509_get_certificate_string
function x509_parse%(cert: opaque of x509%): X509::Certificate
%{
assert(cert);
file_analysis::X509Val* h = (file_analysis::X509Val*) cert;
return file_analysis::X509::ParseCertificate(h);
%}
## Returns the string form of a certificate.
##
## cert: The X509 certificate opaque handle.
##
## pem: A boolean that specifies if the certificate is returned
## in pem-form (true), or as the raw ASN1 encoded binary
## (false).
##
## Returns: X509 certificate as a string.
##
## .. bro:see:: x509_certificate x509_extension x509_ext_basic_constraints
## x509_ext_subject_alternative_name x509_parse x509_verify
function x509_get_certificate_string%(cert: opaque of x509, pem: bool &default=F%): string
%{
assert(cert);
file_analysis::X509Val* h = (file_analysis::X509Val*) cert;
BIO *bio = BIO_new(BIO_s_mem());
if ( pem )
PEM_write_bio_X509(bio, h->GetCertificate());
else
i2d_X509_bio(bio, h->GetCertificate());
StringVal* ext_val = file_analysis::X509::GetExtensionFromBIO(bio);
if ( ! ext_val )
ext_val = new StringVal("");
return ext_val;
%}
## Verifies an OCSP reply.
##
## certs: Specifies the certificate chain to use. Server certificate first.
##
## ocsp_reply: the ocsp reply to validate.
##
## root_certs: A list of root certificates to validate the certificate chain.
##
## verify_time: Time for the validity check of the certificates.
##
## Returns: A record of type X509::Result containing the result code of the
## verify operation.
##
## .. bro:see:: x509_certificate x509_extension x509_ext_basic_constraints
## x509_ext_subject_alternative_name x509_parse
## x509_get_certificate_string x509_verify
function x509_ocsp_verify%(certs: x509_opaque_vector, ocsp_reply: string, root_certs: table_string_of_string, verify_time: time &default=network_time()%): X509::Result
%{
RecordVal* rval = 0;
X509_STORE* ctx = x509_get_root_store(root_certs->AsTableVal());
if ( ! ctx )
return x509_result_record(-1, "Problem initializing root store");
VectorVal *certs_vec = certs->AsVectorVal();
if ( certs_vec->Size() < 1 )
{
reporter->Error("No certificates given in vector");
return x509_result_record(-1, "no certificates");
}
// host certificate
unsigned int index = 0; // to prevent overloading to 0pointer
Val *sv = certs_vec->Lookup(index);
if ( ! sv )
{
builtin_error("undefined value in certificate vector");
return x509_result_record(-1, "undefined value in certificate vector");
}
file_analysis::X509Val* cert_handle = (file_analysis::X509Val*) sv;
X509* cert = cert_handle->GetCertificate();
if ( ! cert )
{
builtin_error(fmt("No certificate in opaque"));
return x509_result_record(-1, "No certificate in opaque");
}
const unsigned char* start = ocsp_reply->Bytes();
STACK_OF(X509)* untrusted_certs = x509_get_untrusted_stack(certs_vec);
if ( ! untrusted_certs )
return x509_result_record(-1, "Problem initializing list of untrusted certificates");
// from here, always goto cleanup. Initialize all other required variables...
time_t vtime = (time_t) verify_time;
OCSP_BASICRESP *basic = 0;
OCSP_SINGLERESP *single = 0;
X509_STORE_CTX *csc = 0;
OCSP_CERTID *certid = 0;
int status = -1;
int out = -1;
int result = -1;
X509* issuer_certificate = 0;
OCSP_RESPONSE *resp = d2i_OCSP_RESPONSE(NULL, &start, ocsp_reply->Len());
if ( ! resp )
{
rval = x509_result_record(-1, "Could not parse OCSP response");
goto x509_ocsp_cleanup;
}
status = OCSP_response_status(resp);
if ( status != OCSP_RESPONSE_STATUS_SUCCESSFUL )
{
rval = x509_result_record(-2, OCSP_response_status_str(status));
goto x509_ocsp_cleanup;
}
basic = OCSP_response_get1_basic(resp);
if ( ! basic )
{
rval = x509_result_record(-1, "Could not parse OCSP response");
goto x509_ocsp_cleanup;
}
// the following code took me _forever_ to get right.
// The OCSP_basic_verify command takes a list of certificates. However (which is not immediately
// visible or understandable), those are only used to find the signer certificate. They are _not_
// used for chain building during the actual verification (this would be stupid). But - if we sneakily
// inject the certificates in the certificate list of the OCSP reply, they actually are used during
// the lookup.
// Yay.
issuer_certificate = 0;
for ( int i = 0; i < sk_X509_num(untrusted_certs); i++)
{
sk_X509_push(basic->certs, X509_dup(sk_X509_value(untrusted_certs, i)));
if ( X509_NAME_cmp(X509_get_issuer_name(cert), X509_get_subject_name(sk_X509_value(untrusted_certs, i))) )
issuer_certificate = sk_X509_value(untrusted_certs, i);
}
// Because we actually want to be able to give nice error messages that show why we were
// not able to verify the OCSP response - do our own verification logic first.
csc = X509_STORE_CTX_new();
X509_STORE_CTX_init(csc, ctx, sk_X509_value(basic->certs, 0), basic->certs);
X509_STORE_CTX_set_time(csc, 0, (time_t) verify_time);
X509_STORE_CTX_set_purpose(csc, X509_PURPOSE_OCSP_HELPER);
result = X509_verify_cert(csc);
if ( result != 1 )
{
const char *reason = X509_verify_cert_error_string((*csc).error);
rval = x509_result_record(result, X509_verify_cert_error_string((*csc).error));
goto x509_ocsp_cleanup;
}
out = OCSP_basic_verify(basic, NULL, ctx, 0);
if ( result < 1 )
{
rval = x509_result_record(out, ERR_error_string(ERR_get_error(),NULL));
goto x509_ocsp_cleanup;
}
// ok, now we verified the OCSP response. This means that we have a valid chain tying it
// to a root that we trust and that the signature also hopefully is valid. This does not yet
// mean that the ocsp response actually matches the certificate the server send us or that
// the OCSP response even says that the certificate is valid.
// let's start this out by checking that the response is actually for the certificate we want
// to validate and not for something completely unrelated that the server is trying to trick us
// into accepting.
if ( issuer_certificate )
certid = OCSP_cert_to_id(NULL, cert, issuer_certificate);
else
{
// issuer not in list sent by server, check store
X509_OBJECT obj;
int lookup = X509_STORE_get_by_subject(csc, X509_LU_X509, X509_get_subject_name(cert), &obj);
if ( lookup <= 0)
{
rval = x509_result_record(lookup, "Could not find issuer of host certificate");
goto x509_ocsp_cleanup;
}
certid = OCSP_cert_to_id(NULL, cert, obj.data.x509);
}
if ( ! certid )
{
rval = x509_result_record(-1, "Certificate ID construction failed");
goto x509_ocsp_cleanup;
}
// for now, assume we have one reply...
single = sk_OCSP_SINGLERESP_value(basic->tbsResponseData->responses, 0);
if ( ! single )
{
rval = x509_result_record(-1, "Could not lookup OCSP response information");
goto x509_ocsp_cleanup;
}
if ( ! OCSP_id_cmp(certid, single->certId) )
return x509_result_record(-1, "OCSP reply is not for host certificate");
// next - check freshness of proof...
if ( ! ASN1_GENERALIZEDTIME_check(single->thisUpdate) || ! ASN1_GENERALIZEDTIME_check(single->nextUpdate) )
{
rval = x509_result_record(-1, "OCSP reply contains invalid dates");
goto x509_ocsp_cleanup;
}
// now - nearly done. Check freshness and status code.
// There is a function to check the freshness of the ocsp reply in the ocsp code of OpenSSL. But - it only
// supports comparing it against the current time, not against arbitrary times. Hence it is kind of unusable
// for us...
// Well, we will do it manually.
if ( X509_cmp_time(single->thisUpdate, &vtime) > 0 )
rval = x509_result_record(-1, "OCSP reply specifies time in future");
else if ( X509_cmp_time(single->nextUpdate, &vtime) < 0 )
rval = x509_result_record(-1, "OCSP reply expired");
else if ( single->certStatus->type != V_OCSP_CERTSTATUS_GOOD )
rval = x509_result_record(-1, OCSP_cert_status_str(single->certStatus->type));
// if we have no error so far, we are done.
if ( !rval )
rval = x509_result_record(1, OCSP_cert_status_str(single->certStatus->type));
x509_ocsp_cleanup:
if ( untrusted_certs )
sk_X509_free(untrusted_certs);
if ( resp )
OCSP_RESPONSE_free(resp);
if ( basic )
OCSP_BASICRESP_free(basic);
if ( csc )
{
X509_STORE_CTX_cleanup(csc);
X509_STORE_CTX_free(csc);
}
if ( certid )
OCSP_CERTID_free(certid);
return rval;
%}
## Verifies a certificate.
##
## certs: Specifies a certificate chain that is being used to validate
## the given certificate against the root store given in *root_certs*.
## The host certificate has to be at index 0.
##
## root_certs: A list of root certificates to validate the certificate chain.
##
## verify_time: Time for the validity check of the certificates.
##
## Returns: A record of type X509::Result containing the result code of the
## verify operation. In case of success also returns the full
## certificate chain.
##
## .. bro:see:: x509_certificate x509_extension x509_ext_basic_constraints
## x509_ext_subject_alternative_name x509_parse
## x509_get_certificate_string x509_ocsp_verify
function x509_verify%(certs: x509_opaque_vector, root_certs: table_string_of_string, verify_time: time &default=network_time()%): X509::Result
%{
X509_STORE* ctx = x509_get_root_store(root_certs->AsTableVal());
if ( ! ctx )
return x509_result_record(-1, "Problem initializing root store");
VectorVal *certs_vec = certs->AsVectorVal();
if ( ! certs_vec || certs_vec->Size() < 1 )
{
reporter->Error("No certificates given in vector");
return x509_result_record(-1, "no certificates");
}
// host certificate
unsigned int index = 0; // to prevent overloading to 0pointer
Val *sv = certs_vec->Lookup(index);
if ( !sv )
{
builtin_error("undefined value in certificate vector");
return x509_result_record(-1, "undefined value in certificate vector");
}
file_analysis::X509Val* cert_handle = (file_analysis::X509Val*) sv;
X509* cert = cert_handle->GetCertificate();
if ( ! cert )
{
builtin_error(fmt("No certificate in opaque"));
return x509_result_record(-1, "No certificate in opaque");
}
STACK_OF(X509)* untrusted_certs = x509_get_untrusted_stack(certs_vec);
if ( ! untrusted_certs )
return x509_result_record(-1, "Problem initializing list of untrusted certificates");
X509_STORE_CTX csc;
X509_STORE_CTX_init(&csc, ctx, cert, untrusted_certs);
X509_STORE_CTX_set_time(&csc, 0, (time_t) verify_time);
X509_STORE_CTX_set_flags(&csc, X509_V_FLAG_USE_CHECK_TIME);
int result = X509_verify_cert(&csc);
VectorVal* chainVector = 0;
if ( result == 1 ) // we have a valid chain. try to get it...
{
STACK_OF(X509)* chain = X509_STORE_CTX_get1_chain(&csc); // get1 = deep copy
if ( ! chain )
{
reporter->Error("Encountered valid chain that could not be resolved");
sk_X509_pop_free(chain, X509_free);
goto x509_verify_chainerror;
}
int num_certs = sk_X509_num(chain);
chainVector = new VectorVal(internal_type("x509_opaque_vector")->AsVectorType());
for ( int i = 0; i < num_certs; i++ )
{
X509* currcert = sk_X509_value(chain, i);
if ( currcert )
// X509Val takes ownership of currcert.
chainVector->Assign(i, new file_analysis::X509Val(currcert));
else
{
reporter->InternalWarning("OpenSSL returned null certificate");
sk_X509_pop_free(chain, X509_free);
goto x509_verify_chainerror;
}
}
sk_X509_free(chain);
}
x509_verify_chainerror:
X509_STORE_CTX_cleanup(&csc);
sk_X509_free(untrusted_certs);
RecordVal* rrecord = x509_result_record(csc.error, X509_verify_cert_error_string(csc.error), chainVector);
return rrecord;
%}

View file

@ -0,0 +1,5 @@
type X509::Certificate: record;
type X509::Extension: record;
type X509::BasicConstraints: record;
type X509::SubjectAlternativeName: record;
type X509::Result: record;

View file

@ -1,6 +1,6 @@
##! Internal functions and types used by the file analysis framework.
module FileAnalysis;
module Files;
%%{
#include "file_analysis/Manager.h"
@ -8,40 +8,48 @@ module FileAnalysis;
type AnalyzerArgs: record;
## :bro:see:`FileAnalysis::set_timeout_interval`.
function FileAnalysis::__set_timeout_interval%(file_id: string, t: interval%): bool
## :bro:see:`Files::set_timeout_interval`.
function Files::__set_timeout_interval%(file_id: string, t: interval%): bool
%{
bool result = file_mgr->SetTimeoutInterval(file_id->CheckString(), t);
return new Val(result, TYPE_BOOL);
%}
## :bro:see:`FileAnalysis::add_analyzer`.
function FileAnalysis::__add_analyzer%(file_id: string, args: any%): bool
## :bro:see:`Files::add_analyzer`.
function Files::__add_analyzer%(file_id: string, tag: Files::Tag, args: any%): bool
%{
using BifType::Record::FileAnalysis::AnalyzerArgs;
using BifType::Record::Files::AnalyzerArgs;
RecordVal* rv = args->AsRecordVal()->CoerceTo(AnalyzerArgs);
bool result = file_mgr->AddAnalyzer(file_id->CheckString(), rv);
bool result = file_mgr->AddAnalyzer(file_id->CheckString(),
file_mgr->GetComponentTag(tag), rv);
Unref(rv);
return new Val(result, TYPE_BOOL);
%}
## :bro:see:`FileAnalysis::remove_analyzer`.
function FileAnalysis::__remove_analyzer%(file_id: string, args: any%): bool
## :bro:see:`Files::remove_analyzer`.
function Files::__remove_analyzer%(file_id: string, tag: Files::Tag, args: any%): bool
%{
using BifType::Record::FileAnalysis::AnalyzerArgs;
using BifType::Record::Files::AnalyzerArgs;
RecordVal* rv = args->AsRecordVal()->CoerceTo(AnalyzerArgs);
bool result = file_mgr->RemoveAnalyzer(file_id->CheckString(), rv);
bool result = file_mgr->RemoveAnalyzer(file_id->CheckString(),
file_mgr->GetComponentTag(tag) , rv);
Unref(rv);
return new Val(result, TYPE_BOOL);
%}
## :bro:see:`FileAnalysis::stop`.
function FileAnalysis::__stop%(file_id: string%): bool
## :bro:see:`Files::stop`.
function Files::__stop%(file_id: string%): bool
%{
bool result = file_mgr->IgnoreFile(file_id->CheckString());
return new Val(result, TYPE_BOOL);
%}
## :bro:see:`Files::analyzer_name`.
function Files::__analyzer_name%(tag: Files::Tag%) : string
%{
return new StringVal(file_mgr->GetComponentName(tag));
%}
module GLOBAL;
## For use within a :bro:see:`get_file_handle` handler to set a unique
@ -58,4 +66,4 @@ function set_file_handle%(handle: string%): any
return 0;
%}
const FileAnalysis::salt: string;
const Files::salt: string;