mirror of
https://github.com/zeek/zeek.git
synced 2025-10-09 01:58:20 +00:00
Add Teredo packet analyzer, disable old analyzer
This commit is contained in:
parent
05574ecce1
commit
dc0ecf9811
25 changed files with 683 additions and 91 deletions
|
@ -25,3 +25,4 @@ add_subdirectory(iptunnel)
|
|||
add_subdirectory(ayiya)
|
||||
add_subdirectory(geneve)
|
||||
add_subdirectory(vxlan)
|
||||
add_subdirectory(teredo)
|
||||
|
|
7
src/packet_analysis/protocol/teredo/CMakeLists.txt
Normal file
7
src/packet_analysis/protocol/teredo/CMakeLists.txt
Normal 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()
|
26
src/packet_analysis/protocol/teredo/Plugin.cc
Normal file
26
src/packet_analysis/protocol/teredo/Plugin.cc
Normal 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
|
280
src/packet_analysis/protocol/teredo/Teredo.cc
Normal file
280
src/packet_analysis/protocol/teredo/Teredo.cc
Normal 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
|
106
src/packet_analysis/protocol/teredo/Teredo.h
Normal file
106
src/packet_analysis/protocol/teredo/Teredo.h
Normal 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
|
55
src/packet_analysis/protocol/teredo/events.bif
Normal file
55
src/packet_analysis/protocol/teredo/events.bif
Normal 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%);
|
||||
|
20
src/packet_analysis/protocol/teredo/functions.bif
Normal file
20
src/packet_analysis/protocol/teredo/functions.bif
Normal 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();
|
||||
%}
|
Loading…
Add table
Add a link
Reference in a new issue