Add Teredo packet analyzer, disable old analyzer

This commit is contained in:
Tim Wojtulewicz 2021-08-27 14:49:15 -07:00
parent 05574ecce1
commit dc0ecf9811
25 changed files with 683 additions and 91 deletions

View file

@ -25,3 +25,4 @@ add_subdirectory(iptunnel)
add_subdirectory(ayiya)
add_subdirectory(geneve)
add_subdirectory(vxlan)
add_subdirectory(teredo)

View file

@ -0,0 +1,7 @@
include(ZeekPlugin)
zeek_plugin_begin(Zeek Teredo)
zeek_plugin_cc(Teredo.cc Plugin.cc)
zeek_plugin_bif(events.bif)
zeek_plugin_bif(functions.bif)
zeek_plugin_end()

View file

@ -0,0 +1,26 @@
// See the file in the main distribution directory for copyright.
#include "zeek/plugin/Plugin.h"
#include "zeek/packet_analysis/Component.h"
#include "zeek/packet_analysis/protocol/teredo/Teredo.h"
namespace zeek::plugin::detail::Zeek_Teredo
{
class Plugin : public zeek::plugin::Plugin
{
public:
zeek::plugin::Configuration Configure() override
{
AddComponent(new zeek::packet_analysis::Component(
"Teredo", zeek::packet_analysis::teredo::TeredoAnalyzer::Instantiate));
zeek::plugin::Configuration config;
config.name = "Zeek::Teredo";
config.description = "Teredo packet analyzer";
return config;
}
} plugin;
} // namespace zeek::plugin::detail::Zeek_Teredo

View file

@ -0,0 +1,280 @@
#include "zeek/packet_analysis/protocol/teredo/Teredo.h"
#include "zeek/Conn.h"
#include "zeek/IP.h"
#include "zeek/RE.h"
#include "zeek/Reporter.h"
#include "zeek/RunState.h"
#include "zeek/TunnelEncapsulation.h"
#include "zeek/ZeekString.h"
#include "zeek/packet_analysis/protocol/ip/IP.h"
#include "zeek/packet_analysis/protocol/iptunnel/IPTunnel.h"
#include "zeek/packet_analysis/protocol/teredo/events.bif.h"
namespace zeek::packet_analysis::teredo
{
namespace detail
{
bool TeredoEncapsulation::DoParse(const u_char* data, size_t& len, bool found_origin,
bool found_auth)
{
if ( len < 2 )
{
Weird("truncated_Teredo");
return false;
}
uint16_t tag = ntohs((*((const uint16_t*)data)));
if ( tag == 0 )
{
// Origin Indication
if ( found_origin )
// can't have multiple origin indications
return false;
if ( len < 8 )
{
Weird("truncated_Teredo_origin_indication");
return false;
}
origin_indication = data;
len -= 8;
data += 8;
return DoParse(data, len, true, found_auth);
}
else if ( tag == 1 )
{
// Authentication
if ( found_origin || found_auth )
// can't have multiple authentication headers and can't come after
// an origin indication
return false;
if ( len < 4 )
{
Weird("truncated_Teredo_authentication");
return false;
}
uint8_t id_len = data[2];
uint8_t au_len = data[3];
uint16_t tot_len = 4 + id_len + au_len + 8 + 1;
if ( len < tot_len )
{
Weird("truncated_Teredo_authentication");
return false;
}
auth = data;
len -= tot_len;
data += tot_len;
return DoParse(data, len, found_origin, true);
}
else if ( ((tag & 0xf000) >> 12) == 6 )
{
// IPv6
if ( len < 40 )
{
Weird("truncated_IPv6_in_Teredo");
return false;
}
// There's at least a possible IPv6 header, we'll decide what to do
// later if the payload length field doesn't match the actual length
// of the packet.
inner_ip = data;
return true;
}
return false;
}
RecordValPtr TeredoEncapsulation::BuildVal(const std::shared_ptr<IP_Hdr>& inner) const
{
static auto teredo_hdr_type = id::find_type<RecordType>("teredo_hdr");
static auto teredo_auth_type = id::find_type<RecordType>("teredo_auth");
static auto teredo_origin_type = id::find_type<RecordType>("teredo_origin");
auto teredo_hdr = make_intrusive<RecordVal>(teredo_hdr_type);
if ( auth )
{
auto teredo_auth = make_intrusive<RecordVal>(teredo_auth_type);
uint8_t id_len = *((uint8_t*)(auth + 2));
uint8_t au_len = *((uint8_t*)(auth + 3));
uint64_t nonce = ntohll(*((uint64_t*)(auth + 4 + id_len + au_len)));
uint8_t conf = *((uint8_t*)(auth + 4 + id_len + au_len + 8));
teredo_auth->Assign(0, new String(auth + 4, id_len, true));
teredo_auth->Assign(1, new String(auth + 4 + id_len, au_len, true));
teredo_auth->Assign(2, nonce);
teredo_auth->Assign(3, conf);
teredo_hdr->Assign(0, std::move(teredo_auth));
}
if ( origin_indication )
{
auto teredo_origin = make_intrusive<RecordVal>(teredo_origin_type);
uint16_t port = ntohs(*((uint16_t*)(origin_indication + 2))) ^ 0xFFFF;
uint32_t addr = ntohl(*((uint32_t*)(origin_indication + 4))) ^ 0xFFFFFFFF;
teredo_origin->Assign(0, val_mgr->Port(port, TRANSPORT_UDP));
teredo_origin->Assign(1, make_intrusive<AddrVal>(htonl(addr)));
teredo_hdr->Assign(1, std::move(teredo_origin));
}
teredo_hdr->Assign(2, inner->ToPktHdrVal());
return teredo_hdr;
}
} // namespace detail
TeredoAnalyzer::TeredoAnalyzer() : zeek::packet_analysis::Analyzer("TEREDO")
{
// The pattern matching below is based on this old DPD signature
// signature dpd_teredo {
// ip-proto = udp
// payload
// /^(\x00\x00)|(\x00\x01)|([\x60-\x6f].{7}((\x20\x01\x00\x00)).{28})|([\x60-\x6f].{23}((\x20\x01\x00\x00))).{12}/
// enable "teredo"
// }
pattern_re = std::make_unique<zeek::detail::Specific_RE_Matcher>(zeek::detail::MATCH_EXACTLY,
1);
pattern_re->AddPat("^(\\x00\\x00)|(\\x00\\x01)|([\\x60-\\x6f].{7}((\\x20\\x01\\x00\\x00)).{28})"
"|([\\x60-\\x6f].{23}((\\x20\\x01\\x00\\x00))).{12}");
pattern_re->Compile();
}
bool TeredoAnalyzer::AnalyzePacket(size_t len, const uint8_t* data, Packet* packet)
{
if ( ! BifConst::Tunnel::enable_teredo )
return false;
// Teredo always comes from a UDP connection, which means that session should always
// be valid and always be a connection. Store this off for the span of the
// processing so that it can be used for other things. Return a weird if we didn't
// have a session stored.
if ( ! packet->session )
{
Analyzer::Weird("teredo_missing_connection");
return false;
}
else if ( AnalyzerViolated(packet->session) )
return false;
if ( packet->encap && packet->encap->Depth() >= BifConst::Tunnel::max_depth )
{
Analyzer::Weird("exceeded_tunnel_max_depth", packet);
return false;
}
conn = static_cast<Connection*>(packet->session);
zeek::detail::ConnKey conn_key = conn->Key();
OrigRespMap::iterator or_it = orig_resp_map.find(conn_key);
if ( or_it == orig_resp_map.end() )
or_it = orig_resp_map.insert(or_it, {conn_key, {}});
detail::TeredoEncapsulation te(this);
if ( ! te.Parse(data, len) )
{
AnalyzerViolation("Bad Teredo encapsulation", conn, (const char*)data, len);
return false;
}
// TODO: i'm not sure about this. on the one hand, we do some error checking with the result
// but on the other hand we duplicate this work here. maybe this header could just be stored
// and reused in the IP analyzer somehow?
std::shared_ptr<IP_Hdr> inner = nullptr;
int rslt = packet_analysis::IP::ParsePacket(len, te.InnerIP(), IPPROTO_IPV6, inner);
if ( rslt > 0 )
{
if ( inner->NextProto() == IPPROTO_NONE && inner->PayloadLen() == 0 )
// Teredo bubbles having data after IPv6 header isn't strictly a
// violation, but a little weird.
Weird("Teredo_bubble_with_payload", true);
else
{
AnalyzerViolation("Teredo payload length", conn, (const char*)data, len);
return false;
}
}
if ( rslt == 0 || rslt > 0 )
{
if ( packet->is_orig )
or_it->second.valid_orig = true;
else
or_it->second.valid_resp = true;
Confirm(or_it->second.valid_orig, or_it->second.valid_resp);
}
else
{
AnalyzerViolation("Truncated Teredo or invalid inner IP version", conn, (const char*)data,
len);
return false;
}
ValPtr teredo_hdr;
if ( teredo_packet )
{
teredo_hdr = te.BuildVal(inner);
packet->session->EnqueueEvent(teredo_packet, nullptr, packet->session->GetVal(),
teredo_hdr);
}
if ( te.Authentication() && teredo_authentication )
{
if ( ! teredo_hdr )
teredo_hdr = te.BuildVal(inner);
packet->session->EnqueueEvent(teredo_authentication, nullptr, packet->session->GetVal(),
teredo_hdr);
}
if ( te.OriginIndication() && teredo_origin_indication )
{
if ( ! teredo_hdr )
teredo_hdr = te.BuildVal(inner);
packet->session->EnqueueEvent(teredo_origin_indication, nullptr, packet->session->GetVal(),
teredo_hdr);
}
if ( inner->NextProto() == IPPROTO_NONE && teredo_bubble )
{
if ( ! teredo_hdr )
teredo_hdr = te.BuildVal(inner);
packet->session->EnqueueEvent(teredo_bubble, nullptr, packet->session->GetVal(),
teredo_hdr);
}
int encap_index = 0;
auto inner_packet = packet_analysis::IPTunnel::build_inner_packet(
packet, &encap_index, nullptr, len, te.InnerIP(), DLT_RAW, BifEnum::Tunnel::TEREDO,
GetAnalyzerTag());
return ForwardPacket(len, te.InnerIP(), inner_packet.get());
}
bool TeredoAnalyzer::DetectProtocol(size_t len, const uint8_t* data, Packet* packet)
{
if ( ! BifConst::Tunnel::enable_teredo )
return false;
if ( ! pattern_re->Match(data, len) )
return false;
return true;
}
} // namespace zeek::packet_analysis::teredo

View file

@ -0,0 +1,106 @@
#pragma once
#include <map>
#include "zeek/Conn.h"
#include "zeek/NetVar.h"
#include "zeek/RE.h"
#include "zeek/Reporter.h"
#include "zeek/packet_analysis/Analyzer.h"
namespace zeek::packet_analysis::teredo
{
class TeredoAnalyzer final : public packet_analysis::Analyzer
{
public:
TeredoAnalyzer();
~TeredoAnalyzer() override = default;
bool AnalyzePacket(size_t len, const uint8_t* data, Packet* packet) override;
static zeek::packet_analysis::AnalyzerPtr Instantiate()
{
return std::make_shared<TeredoAnalyzer>();
}
/**
* Emits a weird only if the analyzer has previously been able to
* decapsulate a Teredo packet in both directions or if *force* param is
* set, since otherwise the weirds could happen frequently enough to be less
* than helpful. The *force* param is meant for cases where just one side
* has a valid encapsulation and so the weird would be informative.
*/
void Weird(const char* name, bool force = false) const
{
if ( AnalyzerConfirmed(conn) || force )
reporter->Weird(conn, name, "", GetAnalyzerName());
}
/**
* If the delayed confirmation option is set, then a valid encapsulation
* seen from both end points is required before confirming.
*/
void Confirm(bool valid_orig, bool valid_resp)
{
if ( ! BifConst::Tunnel::delay_teredo_confirmation || (valid_orig && valid_resp) )
{
AnalyzerConfirmation(conn);
}
}
bool DetectProtocol(size_t len, const uint8_t* data, Packet* packet) override;
void RemoveConnection(const zeek::detail::ConnKey& conn_key) { orig_resp_map.erase(conn_key); }
protected:
Connection* conn = nullptr;
struct OrigResp
{
bool valid_orig = false;
bool valid_resp = false;
bool confirmed = false;
};
using OrigRespMap = std::map<zeek::detail::ConnKey, OrigResp>;
OrigRespMap orig_resp_map;
std::unique_ptr<zeek::detail::Specific_RE_Matcher> pattern_re;
};
namespace detail
{
class TeredoEncapsulation
{
public:
explicit TeredoEncapsulation(const TeredoAnalyzer* ta) : analyzer(ta) { }
/**
* Returns whether input data parsed as a valid Teredo encapsulation type.
* If it was valid, the len argument is decremented appropriately.
*/
bool Parse(const u_char* data, size_t& len) { return DoParse(data, len, false, false); }
const u_char* InnerIP() const { return inner_ip; }
const u_char* OriginIndication() const { return origin_indication; }
const u_char* Authentication() const { return auth; }
RecordValPtr BuildVal(const std::shared_ptr<IP_Hdr>& inner) const;
private:
bool DoParse(const u_char* data, size_t& len, bool found_orig, bool found_au);
void Weird(const char* name) const { analyzer->Weird(name); }
const u_char* inner_ip = nullptr;
const u_char* origin_indication = nullptr;
const u_char* auth = nullptr;
const TeredoAnalyzer* analyzer = nullptr;
};
} // namespace detail
} // namespace zeek::packet_analysis::teredo

View file

@ -0,0 +1,55 @@
## Generated for any IPv6 packet encapsulated in a Teredo tunnel.
## See :rfc:`4380` for more information about the Teredo protocol.
##
## outer: The Teredo tunnel connection.
##
## inner: The Teredo-encapsulated IPv6 packet header and transport header.
##
## .. zeek:see:: teredo_authentication teredo_origin_indication teredo_bubble
##
## .. note:: Since this event may be raised on a per-packet basis, handling
## it may become particularly expensive for real-time analysis.
event teredo_packet%(outer: connection, inner: teredo_hdr%);
## Generated for IPv6 packets encapsulated in a Teredo tunnel that
## use the Teredo authentication encapsulation method.
## See :rfc:`4380` for more information about the Teredo protocol.
##
## outer: The Teredo tunnel connection.
##
## inner: The Teredo-encapsulated IPv6 packet header and transport header.
##
## .. zeek:see:: teredo_packet teredo_origin_indication teredo_bubble
##
## .. note:: Since this event may be raised on a per-packet basis, handling
## it may become particularly expensive for real-time analysis.
event teredo_authentication%(outer: connection, inner: teredo_hdr%);
## Generated for IPv6 packets encapsulated in a Teredo tunnel that
## use the Teredo origin indication encapsulation method.
## See :rfc:`4380` for more information about the Teredo protocol.
##
## outer: The Teredo tunnel connection.
##
## inner: The Teredo-encapsulated IPv6 packet header and transport header.
##
## .. zeek:see:: teredo_packet teredo_authentication teredo_bubble
##
## .. note:: Since this event may be raised on a per-packet basis, handling
## it may become particularly expensive for real-time analysis.
event teredo_origin_indication%(outer: connection, inner: teredo_hdr%);
## Generated for Teredo bubble packets. That is, IPv6 packets encapsulated
## in a Teredo tunnel that have a Next Header value of :zeek:id:`IPPROTO_NONE`.
## See :rfc:`4380` for more information about the Teredo protocol.
##
## outer: The Teredo tunnel connection.
##
## inner: The Teredo-encapsulated IPv6 packet header and transport header.
##
## .. zeek:see:: teredo_packet teredo_authentication teredo_origin_indication
##
## .. note:: Since this event may be raised on a per-packet basis, handling
## it may become particularly expensive for real-time analysis.
event teredo_bubble%(outer: connection, inner: teredo_hdr%);

View file

@ -0,0 +1,20 @@
module PacketAnalyzer::TEREDO;
%%{
#include "zeek/Conn.h"
#include "zeek/session/Manager.h"
#include "zeek/packet_analysis/Manager.h"
#include "zeek/packet_analysis/protocol/teredo/Teredo.h"
%%}
function remove_teredo_connection%(cid: conn_id%) : bool
%{
zeek::packet_analysis::AnalyzerPtr teredo = zeek::packet_mgr->GetAnalyzer("Teredo");
if ( teredo )
{
zeek::detail::ConnKey conn_key(cid);
static_cast<zeek::packet_analysis::teredo::TeredoAnalyzer*>(teredo.get())->RemoveConnection(conn_key);
}
return zeek::val_mgr->True();
%}