#define DCE2_UDP_NAME "dce_udp"
#define DCE2_UDP_HELP "dce over udp inspection"
+#define DCE2_MOCK_HDR_LEN__CL (sizeof(DceRpcClHdr))
+
struct dce2UdpStats
{
/* The common stats block has to be at the beginning followed
extern THREAD_LOCAL ProfileStats dce2_udp_pstat_cl_acts;
extern THREAD_LOCAL ProfileStats dce2_udp_pstat_cl_frag;
extern THREAD_LOCAL ProfileStats dce2_udp_pstat_cl_reass;
+extern THREAD_LOCAL Packet* dce2_udp_rpkt;
struct DceRpcClHdr /* Connectionless header */
{
uint16_t fragnum;
uint8_t auth_proto;
uint8_t serial_lo;
-
};
enum DceRpcClFlags1
DCERPC_CL_FLAGS1__RESERVED_80 = 0x80
};
-inline uint8_t DceRpcClRpcVers(const DceRpcClHdr *cl)
+inline uint8_t DceRpcClRpcVers(const DceRpcClHdr* cl)
{
return cl->rpc_vers;
}
-inline uint8_t DceRpcClPduType(const DceRpcClHdr *cl)
+inline uint8_t DceRpcClPduType(const DceRpcClHdr* cl)
{
return cl->ptype;
}
-inline DceRpcBoFlag DceRpcClByteOrder(const DceRpcClHdr *cl)
+inline DceRpcBoFlag DceRpcClByteOrder(const DceRpcClHdr* cl)
{
return DceRpcByteOrder(cl->drep[0]);
}
-inline uint16_t DceRpcClLen(const DceRpcClHdr *cl)
+inline uint16_t DceRpcClLen(const DceRpcClHdr* cl)
{
return DceRpcNtohs(&cl->len, DceRpcClByteOrder(cl));
}
-inline uint16_t DceRpcClOpnum(const DceRpcClHdr *cl)
+inline uint16_t DceRpcClOpnum(const DceRpcClHdr* cl)
{
return DceRpcNtohs(&cl->opnum, DceRpcClByteOrder(cl));
}
-inline uint32_t DceRpcClSeqNum(const DceRpcClHdr *cl)
+inline uint32_t DceRpcClSeqNum(const DceRpcClHdr* cl)
{
return DceRpcNtohl(&cl->seqnum, DceRpcClByteOrder(cl));
}
-inline const Uuid * DceRpcClIface(const DceRpcClHdr *cl)
+inline const Uuid* DceRpcClIface(const DceRpcClHdr* cl)
{
return &cl->if_id;
}
-inline uint32_t DceRpcClIfaceVers(const DceRpcClHdr *cl)
+inline uint32_t DceRpcClIfaceVers(const DceRpcClHdr* cl)
{
return DceRpcNtohl(&cl->if_vers, DceRpcClByteOrder(cl));
}
-inline uint16_t DceRpcClFragNum(const DceRpcClHdr *cl)
+inline uint16_t DceRpcClFragNum(const DceRpcClHdr* cl)
{
return DceRpcNtohs(&cl->fragnum, DceRpcClByteOrder(cl));
}
-inline int DceRpcClFragFlag(const DceRpcClHdr *cl)
+inline int DceRpcClFragFlag(const DceRpcClHdr* cl)
{
return cl->flags1 & DCERPC_CL_FLAGS1__FRAG;
}
-inline bool DceRpcClFirstFrag(const DceRpcClHdr *cl)
+inline bool DceRpcClFirstFrag(const DceRpcClHdr* cl)
{
return (DceRpcClFragFlag(cl) && (DceRpcClFragNum(cl) == 0));
}
-inline int DceRpcClLastFrag(const DceRpcClHdr *cl)
+inline int DceRpcClLastFrag(const DceRpcClHdr* cl)
{
return cl->flags1 & DCERPC_CL_FLAGS1__LASTFRAG;
}
-inline bool DceRpcClFrag(const DceRpcClHdr *cl)
+inline bool DceRpcClFrag(const DceRpcClHdr* cl)
{
if (DceRpcClFragFlag(cl))
{
DCE2_UdpSsnData* get_dce2_udp_session_data(Flow*);
-void DCE2_ClProcess(DCE2_SsnData *sd, DCE2_ClTracker *clt);
+void DCE2_ClProcess(DCE2_SsnData* sd, DCE2_ClTracker* clt);
+void DCE2_ClInitRdata(uint8_t*);
#endif
#include "flow/session.h"
#include "main/snort_debug.h"
+#include "utils/safec.h"
#include "utils/util.h"
#include "dce_common.h"
/********************************************************************
* Structures
********************************************************************/
+struct DCE2_ClFragNode
+{
+ uint32_t frag_number;
+ uint16_t frag_len;
+ uint8_t* frag_data;
+};
+
struct DCE2_ClFragTracker
{
Uuid iface; /* only set on first fragment received */
static DCE2_ClActTracker* DCE2_ClInsertActTracker(DCE2_ClTracker*, DceRpcClHdr*);
static void DCE2_ClRequest(DCE2_SsnData*, DCE2_ClActTracker*, DceRpcClHdr*,
const uint8_t*, uint16_t);
+static void DCE2_ClHandleFrag(DCE2_SsnData*, DCE2_ClActTracker*,
+ DceRpcClHdr*, const uint8_t*, uint16_t);
+static void DCE2_ClFragReassemble(DCE2_SsnData*, DCE2_ClActTracker*, const DceRpcClHdr*);
+static void DCE2_ClResetFragTracker(DCE2_ClFragTracker*);
+static void DCE2_ClSetRdata(DCE2_ClActTracker*, const DceRpcClHdr*, uint8_t*, uint16_t);
/* Callbacks */
static void DCE2_ClActDataFree(void*);
static void DCE2_ClActKeyFree(void*);
+static int DCE2_ClFragCompare(const void*, const void*);
+static void DCE2_ClFragDataFree(void*);
// Main entry point for connectionless DCE/RPC processing. Gets
// the activity tracker associated with this session and passes
if (DceRpcClSeqNum(cl_hdr) == at->seq_num)
{
- // FIXIT-M uncomment once fragments is ported
- //DCE2_ClResetFragTracker(&at->frag_tracker);
+ DCE2_ClResetFragTracker(&at->frag_tracker);
at->seq_num_invalid = 1;
}
{
/* Insert a new activity tracker */
at = DCE2_ClInsertActTracker(clt, cl_hdr);
- if (at == nullptr)
- return nullptr;
}
return at;
}
static void DCE2_ClRequest(DCE2_SsnData* sd, DCE2_ClActTracker* at, DceRpcClHdr* cl_hdr,
- const uint8_t*, uint16_t)
+ const uint8_t* data_ptr, uint16_t data_len)
{
const uint32_t seq_num = DceRpcClSeqNum(cl_hdr);
/* If there are any fragments, the new sequence number invalidates
* all of the frags that might be currently stored. */
- // FIXIT-M uncomment when porting fragments support
- // DCE2_ClResetFragTracker(&at->frag_tracker);
+ DCE2_ClResetFragTracker(&at->frag_tracker);
}
else if ((seq_num < at->seq_num) || at->seq_num_invalid)
{
DCE2_ResetRopts(&sd->ropts);
- if (!DceRpcClFrag(cl_hdr))
+ if (!DceRpcClFrag(cl_hdr)) /* It's a full request */
{
- // FIXIT-M add fragments cleanup
-
- if (seq_num != DCE2_CL__MAX_SEQ_NUM)
+ if ((at->frag_tracker.frags != nullptr) &&
+ !DCE2_ListIsEmpty(at->frag_tracker.frags))
+ {
+ /* If we get a full request, i.e. not a frag, any frags
+ * we have collected are invalidated */
+ DCE2_ClResetFragTracker(&at->frag_tracker);
+ }
+ else if (seq_num != DCE2_CL__MAX_SEQ_NUM)
{
/* This sequence number is now invalid. 0xffffffff is the end of
* the sequence number space and can be reused */
/* Got the last sequence number in the sequence number space */
dce2_udp_stats.cl_max_seqnum++;
}
- } // FIXIT-M add else - fragments path
+ }
+ else /* It's a frag */
+ {
+ dce2_udp_stats.cl_fragments++;
+ if (DCE2_GcDceDefrag((dce2CommonProtoConf*)sd->config))
+ {
+ DCE2_ClHandleFrag(sd, at, cl_hdr, data_ptr, data_len);
+ return;
+ }
+ }
/* Cache relevant values for rule option processing */
sd->ropts.first_frag = DceRpcClFirstFrag(cl_hdr);
snort_free(key);
}
+// Handles connectionless fragments. Creates a new fragment list
+// if necessary and inserts fragment into list. Sets rule option
+// values based on the fragment.
+static void DCE2_ClHandleFrag(DCE2_SsnData* sd, DCE2_ClActTracker* at, DceRpcClHdr* cl_hdr,
+ const uint8_t* data_ptr, uint16_t data_len)
+{
+ DCE2_ClFragTracker* ft = &at->frag_tracker;
+ DCE2_ClFragNode* fn;
+ uint16_t frag_len;
+ int status;
+
+ Profile profile(dce2_udp_pstat_cl_frag);
+
+ /* If the frag length is less than data length there might be authentication
+ * data that we don't want to include, otherwise just set to data len */
+ if (DceRpcClLen(cl_hdr) < data_len)
+ frag_len = DceRpcClLen(cl_hdr);
+ else
+ frag_len = data_len;
+
+ if (frag_len == 0)
+ {
+ return;
+ }
+
+ if (frag_len > dce2_udp_stats.cl_max_frag_size)
+ dce2_udp_stats.cl_max_frag_size = frag_len;
+
+ if (DCE2_GcMaxFrag((dce2CommonProtoConf*)sd->config)
+ && (frag_len > DCE2_GcMaxFragLen((dce2CommonProtoConf*)sd->config)))
+ frag_len = DCE2_GcMaxFragLen((dce2CommonProtoConf*)sd->config);
+
+ if (ft->frags == nullptr)
+ {
+ /* Create new list if we don't have one already */
+ ft->frags = DCE2_ListNew(DCE2_LIST_TYPE__SORTED, DCE2_ClFragCompare, DCE2_ClFragDataFree,
+ nullptr, DCE2_LIST_FLAG__NO_DUPS | DCE2_LIST_FLAG__INS_TAIL);
+
+ if (ft->frags == nullptr)
+ {
+ return;
+ }
+ }
+ else
+ {
+ /* If we already have a fragment in the list with the same fragment number,
+ * that fragment will take precedence over this fragment and this fragment
+ * will not be used by the server */
+ fn = (DCE2_ClFragNode*)DCE2_ListFind(ft->frags, (void*)(uintptr_t)DceRpcClFragNum(cl_hdr));
+ if (fn != nullptr)
+ {
+ return;
+ }
+ }
+
+ /* Create a new frag node to insert into the list */
+ fn = (DCE2_ClFragNode*)snort_calloc(sizeof(DCE2_ClFragNode));
+ fn->frag_number = DceRpcClFragNum(cl_hdr);
+ fn->frag_len = frag_len;
+
+ /* Allocate space for the fragment data */
+ fn->frag_data = (uint8_t*)snort_calloc(frag_len);
+
+ /* Copy the fragment data in the packet to the space just allocated */
+ memcpy(fn->frag_data, data_ptr, frag_len);
+
+ if (DCE2_ListIsEmpty(ft->frags))
+ {
+ /* If this is the first fragment we've received, set interface uuid */
+ DCE2_CopyUuid(&ft->iface, DceRpcClIface(cl_hdr), DceRpcClByteOrder(cl_hdr));
+ ft->iface_vers = DceRpcClIfaceVers(cl_hdr);
+ }
+
+ if (DceRpcClLastFrag(cl_hdr))
+ {
+ /* Set number of expected frags on last frag */
+ ft->num_expected_frags = DceRpcClFragNum(cl_hdr) + 1;
+ }
+ else if (DceRpcClFirstFrag(cl_hdr))
+ {
+ /* Set opum and byte order on first frag */
+ ft->opnum = DceRpcClOpnum(cl_hdr);
+ ft->data_byte_order = DceRpcClByteOrder(cl_hdr);
+ }
+
+ /* Insert frag node into the list */
+ status = DCE2_ListInsert(ft->frags, (void*)(uintptr_t)fn->frag_number, (void*)fn);
+ if (status != DCE2_RET__SUCCESS)
+ {
+ snort_free((void*)fn->frag_data);
+ snort_free((void*)fn);
+
+ DCE2_ClFragReassemble(sd, at, cl_hdr);
+ return;
+ }
+
+ /* Fragment number field in header is uint16_t */
+ if ((ft->num_expected_frags != DCE2_SENTINEL) &&
+ (uint16_t)ft->frags->num_nodes == (uint16_t)ft->num_expected_frags)
+ {
+ /* We got all of the frags - reassemble */
+ DCE2_ClFragReassemble(sd, at, cl_hdr);
+ at->seq_num_invalid = 1;
+
+ return;
+ }
+
+ /* Cache relevant values for rule option processing */
+ sd->ropts.first_frag = DceRpcClFirstFrag(cl_hdr);
+ DCE2_CopyUuid(&sd->ropts.iface, &ft->iface, DCERPC_BO_FLAG__NONE);
+ sd->ropts.iface_vers = ft->iface_vers;
+ DceEndianness* endianness = (DceEndianness*)sd->wire_pkt->endianness;
+ endianness->hdr_byte_order = DceRpcClByteOrder(cl_hdr);
+
+ if (ft->data_byte_order != DCE2_SENTINEL)
+ endianness->data_byte_order = ft->data_byte_order;
+ else
+ endianness->data_byte_order = DceRpcClByteOrder(cl_hdr);
+
+ if (ft->opnum != DCE2_SENTINEL)
+ sd->ropts.opnum = ft->opnum;
+ else
+ sd->ropts.opnum = DceRpcClOpnum(cl_hdr);
+
+ sd->ropts.stub_data = (uint8_t*)cl_hdr + sizeof(DceRpcClHdr);
+
+ DCE2_Detect(sd);
+}
+
+/********************************************************************
+ * Function: DCE2_ClFragCompare()
+ *
+ * Callback to fragment list for sorting the nodes in the list
+ * by fragment number. Values passed in are the fragment numbers.
+ *
+ * Arguments:
+ * const void *
+ * First fragment number to compare.
+ * const void *
+ * Second fragment number to compare.
+ *
+ * Returns:
+ * int
+ * 1 if first value is greater than second value
+ * -1 if first value is less than second value
+ * 0 if first value equals second value
+ *
+ ********************************************************************/
+static int DCE2_ClFragCompare(const void* a, const void* b)
+{
+ const int x = (int)(uintptr_t)a;
+ const int y = (int)(uintptr_t)b;
+
+ if (x > y)
+ return 1;
+ if (x < y)
+ return -1;
+
+ return 0;
+}
+
+// Reassembles fragments into reassembly buffer and copies to
+// reassembly packet.
+static void DCE2_ClFragReassemble(DCE2_SsnData* sd, DCE2_ClActTracker* at, const
+ DceRpcClHdr* cl_hdr)
+{
+ uint8_t dce2_cl_rbuf[IP_MAXPACKET];
+ DCE2_ClFragTracker* ft = &at->frag_tracker;
+ uint8_t* rdata = dce2_cl_rbuf;
+ uint16_t rlen = sizeof(dce2_cl_rbuf);
+ DCE2_ClFragNode* fnode;
+ uint32_t stub_len = 0;
+
+ Profile profile(dce2_udp_pstat_cl_reass);
+
+ for (fnode = (DCE2_ClFragNode*)DCE2_ListFirst(ft->frags);
+ fnode != nullptr;
+ fnode = (DCE2_ClFragNode*)DCE2_ListNext(ft->frags))
+ {
+ if (fnode->frag_len > rlen)
+ {
+ DebugFormat(DEBUG_DCE_UDP,
+ "%s(%d) Size of fragments exceeds reassembly buffer size. "
+ "Using as many fragments as will fit.", __FILE__, __LINE__);
+ break;
+ }
+
+ memcpy(rdata, fnode->frag_data, fnode->frag_len);
+ DCE2_MOVE(rdata, rlen, fnode->frag_len);
+ stub_len += fnode->frag_len;
+ }
+
+ Packet* rpkt = DCE2_GetRpkt(sd->wire_pkt, DCE2_RPKT_TYPE__UDP_CL_FRAG, dce2_cl_rbuf, stub_len);
+ if (rpkt == nullptr)
+ {
+ DebugFormat(DEBUG_DCE_UDP,
+ "%s(%d) Failed to create reassembly packet.",
+ __FILE__, __LINE__);
+ return;
+ }
+
+ DCE2_ClSetRdata(at, cl_hdr, (uint8_t*)rpkt->data,
+ (uint16_t)(rpkt->dsize - DCE2_MOCK_HDR_LEN__CL));
+
+ const uint8_t* stub_data = rpkt->data + DCE2_MOCK_HDR_LEN__CL;
+
+ if (DCE2_PushPkt(rpkt, sd) != DCE2_RET__SUCCESS)
+ {
+ DebugFormat(DEBUG_DCE_UDP,
+ "%s(%d) Failed to push packet onto packet stack.",
+ __FILE__, __LINE__);
+ return;
+ }
+
+ /* Cache relevant values for rule option processing */
+ sd->ropts.first_frag = 1;
+ DCE2_CopyUuid(&sd->ropts.iface, &ft->iface, DCERPC_BO_FLAG__NONE);
+ sd->ropts.iface_vers = ft->iface_vers;
+ DceEndianness* endianness = (DceEndianness*)sd->wire_pkt->endianness;
+ endianness->hdr_byte_order = DceRpcClByteOrder(cl_hdr);
+
+ if (ft->data_byte_order != DCE2_SENTINEL)
+ endianness->data_byte_order = ft->data_byte_order;
+ else
+ endianness->data_byte_order = DceRpcClByteOrder(cl_hdr);
+
+ if (ft->opnum != DCE2_SENTINEL)
+ sd->ropts.opnum = ft->opnum;
+ else
+ sd->ropts.opnum = DceRpcClOpnum(cl_hdr);
+
+ sd->ropts.stub_data = stub_data;
+
+ DCE2_Detect(sd);
+ DCE2_PopPkt(sd);
+
+ dce2_udp_stats.cl_frag_reassembled++;
+}
+
+// Callback to fragment list for freeing data kept in list. Need
+// to free the frag node and the data attached to it.
+static void DCE2_ClFragDataFree(void* data)
+{
+ DCE2_ClFragNode* fn = (DCE2_ClFragNode*)data;
+
+ if (fn == nullptr)
+ return;
+
+ if (fn->frag_data != nullptr)
+ snort_free((void*)fn->frag_data);
+
+ snort_free((void*)fn);
+}
+
+// Destroys the fragment tracker's fragment list and resets opnum,
+// byte order and number of expected frags to a sentinel.
+static void DCE2_ClResetFragTracker(DCE2_ClFragTracker* ft)
+{
+ if (ft == nullptr)
+ return;
+
+ if (ft->frags != nullptr)
+ {
+ DCE2_ListDestroy(ft->frags);
+ ft->frags = nullptr;
+ }
+
+ ft->opnum = DCE2_SENTINEL;
+ ft->data_byte_order = DCE2_SENTINEL;
+ ft->num_expected_frags = DCE2_SENTINEL;
+}
+
+void DCE2_ClInitRdata(uint8_t* buf)
+{
+ DceRpcClHdr* cl_hdr = (DceRpcClHdr*)buf;
+
+ /* Set some relevant fields. These should never get reset */
+ cl_hdr->rpc_vers = DCERPC_PROTO_MAJOR_VERS__4;
+ cl_hdr->ptype = DCERPC_PDU_TYPE__REQUEST;
+ cl_hdr->drep[0] = 0x10; /* Little endian */
+}
+
+// Sets relevant data fields in the reassembly packet.
+static void DCE2_ClSetRdata(DCE2_ClActTracker* at, const DceRpcClHdr* pkt_cl_hdr,
+ uint8_t* cl_ptr, uint16_t stub_len)
+{
+ DCE2_ClFragTracker* ft = &at->frag_tracker;
+ DceRpcClHdr* cl_hdr = (DceRpcClHdr*)cl_ptr;
+ const uint16_t opnum = (ft->opnum != DCE2_SENTINEL) ? (uint16_t)ft->opnum : DceRpcClOpnum(
+ pkt_cl_hdr);
+
+ cl_hdr->len = DceRpcHtons(&stub_len, DCERPC_BO_FLAG__LITTLE_ENDIAN);
+ DCE2_CopyUuid(&cl_hdr->object, &pkt_cl_hdr->object, DceRpcClByteOrder(cl_hdr));
+ DCE2_CopyUuid(&cl_hdr->if_id, &ft->iface, DCERPC_BO_FLAG__LITTLE_ENDIAN);
+ DCE2_CopyUuid(&cl_hdr->act_id, &at->act, DCERPC_BO_FLAG__LITTLE_ENDIAN);
+ cl_hdr->if_vers = DceRpcHtonl(&ft->iface_vers, DCERPC_BO_FLAG__LITTLE_ENDIAN);
+ cl_hdr->opnum = DceRpcHtons(&opnum, DCERPC_BO_FLAG__LITTLE_ENDIAN);
+}
+