From: Russ Combs (rucombs) Date: Fri, 4 Mar 2016 15:55:39 +0000 (-0500) Subject: Merge pull request #308 in SNORT/snort3 from dce to master X-Git-Tag: 3.0.0-233~558 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b9f7ff6ba2dcfc00ddee64e6160acf8522242666;p=thirdparty%2Fsnort3.git Merge pull request #308 in SNORT/snort3 from dce to master Squashed commit of the following: commit 6b30e2a5302afcca4f220f84a8ee022f1d7e4b6c Author: rrp Date: Thu Mar 3 09:09:13 2016 -0500 Changes based on code-review comments commit 8814f03f78443986d3179fd6962b350ef57d1086 Author: rrp Date: Mon Feb 22 11:06:51 2016 -0500 DCE over TCP support for single(non-fragmented) PDU packet processing. Tested with pcap with 4 DCE packets - Bind, Bind Ack, Request and Response. Verified that all packets get decoded and processed properly. Verified debugs, counters(stats) and profile stats --- diff --git a/src/events/event_queue.h b/src/events/event_queue.h index d35cb62f4..0b4681f62 100644 --- a/src/events/event_queue.h +++ b/src/events/event_queue.h @@ -58,8 +58,8 @@ SO_PUBLIC int SnortEventqAdd(const struct OptTreeNode*); SO_PUBLIC int SnortEventqAdd(uint32_t gid, uint32_t sid, RuleType = RULE_TYPE__NONE); SO_PUBLIC bool event_is_enabled(uint32_t gid, uint32_t sid); -void SnortEventqPush(void); -void SnortEventqPop(void); +SO_PUBLIC void SnortEventqPush(void); +SO_PUBLIC void SnortEventqPop(void); #endif diff --git a/src/service_inspectors/dce_rpc/dce_co.cc b/src/service_inspectors/dce_rpc/dce_co.cc index 46bd5fdc8..f53043374 100644 --- a/src/service_inspectors/dce_rpc/dce_co.cc +++ b/src/service_inspectors/dce_rpc/dce_co.cc @@ -20,6 +20,15 @@ // based on work by Todd Wease #include "dce_co.h" +#include "dce_tcp.h" +#include "dce_smb.h" +#include "dce_list.h" +#include "dce_utils.h" +#include "profiler/profiler.h" +#include "main/snort_debug.h" +#include "log/messages.h" + +THREAD_LOCAL int co_reassembled = 0; /******************************************************************** * Function: DCE2_CoInitTracker() @@ -29,9 +38,9 @@ * these fields have been set. * ********************************************************************/ -void DCE2_CoInitTracker(DCE2_CoTracker *cot) +void DCE2_CoInitTracker(DCE2_CoTracker* cot) { - if (cot == NULL) + if (cot == nullptr) return; cot->max_xmit_frag = DCE2_SENTINEL; @@ -39,7 +48,7 @@ void DCE2_CoInitTracker(DCE2_CoTracker *cot) cot->ctx_id = DCE2_SENTINEL; cot->opnum = DCE2_SENTINEL; cot->call_id = DCE2_SENTINEL; - cot->stub_data = NULL; + cot->stub_data = nullptr; cot->got_bind = 0; cot->frag_tracker.opnum = DCE2_SENTINEL; @@ -49,3 +58,1312 @@ void DCE2_CoInitTracker(DCE2_CoTracker *cot) cot->frag_tracker.expected_ctx_id = DCE2_SENTINEL; } +/******************************************************************** + * Function: DCE2_CoResetFragTracker() + * + * Resets frag tracker fields after having reassembled. + * + ********************************************************************/ +static inline void DCE2_CoResetFragTracker(DCE2_CoFragTracker* ft) +{ + if (ft == nullptr) + return; + + ft->opnum = DCE2_SENTINEL; + ft->ctx_id = DCE2_SENTINEL; + ft->expected_call_id = DCE2_SENTINEL; + ft->expected_ctx_id = DCE2_SENTINEL; + ft->expected_opnum = DCE2_SENTINEL; +} + +/******************************************************************** + * Function: DCE2_CoResetTracker() + * + * Resets fields that are transient for requests after the bind or + * alter context. The context id and opnum are dependent on the + * request and in the case of fragmented requests are set until all + * fragments are received. If we got a full request or all of the + * fragments, these should be reset. + * + ********************************************************************/ +static inline void DCE2_CoResetTracker(DCE2_CoTracker* cot) +{ + if (cot == nullptr) + return; + + cot->ctx_id = DCE2_SENTINEL; + cot->opnum = DCE2_SENTINEL; + cot->call_id = DCE2_SENTINEL; + cot->stub_data = nullptr; + + DCE2_CoResetFragTracker(&cot->frag_tracker); +} + +static inline bool DCE2_CoIsSegBuf(DCE2_SsnData* sd, DCE2_CoTracker* cot, const uint8_t* ptr) +{ + DCE2_Buffer* seg_buf; + + if (DCE2_SsnFromServer(sd->wire_pkt)) + seg_buf = cot->srv_seg.buf; + else + seg_buf = cot->cli_seg.buf; + + if (DCE2_BufferIsEmpty(seg_buf)) + return 0; + + /* See if we're looking at a segmentation buffer */ + if ((ptr < DCE2_BufferData(seg_buf)) || + (ptr > (DCE2_BufferData(seg_buf) + DCE2_BufferLength(seg_buf)))) + { + return 0; + } + + return 1; +} + +static inline DCE2_CoSeg* DCE2_CoGetSegPtr(DCE2_SsnData* sd, DCE2_CoTracker* cot) +{ + if (DCE2_SsnFromServer(sd->wire_pkt)) + return &cot->srv_seg; + + return &cot->cli_seg; +} + +/******************************************************************** + * Function: DCE2_CoSetIface() + * + * Sets the interface UUID for the rules options. Looks in the + * context id list. If nothing found there, it looks in the pending + * list (in case we never saw the server response because of + * missed packets) to see if something is there. + * + ********************************************************************/ +static DCE2_Ret DCE2_CoSetIface(DCE2_SsnData* sd, DCE2_CoTracker* cot, uint16_t ctx_id) +{ + DCE2_CoCtxIdNode* ctx_id_node; + + /* This should be set if we've gotten a Bind */ + if (cot->ctx_ids == nullptr) + return DCE2_RET__ERROR; + if (sd->trans == DCE2_TRANS_TYPE__TCP) + { + Profile profile(dce2_tcp_pstat_co_ctx); + } + else + { + Profile profile(dce2_smb_pstat_co_ctx); + } + + ctx_id_node = (DCE2_CoCtxIdNode*)DCE2_ListFind(cot->ctx_ids, (void*)(uintptr_t)ctx_id); + if (ctx_id_node == nullptr) /* context id not found in list */ + { + /* See if it's in the queue. An easy evasion would be to stagger the writes + * and reads such that we see a request before seeing the server bind ack */ + if (cot->pending_ctx_ids != nullptr) + { + for (ctx_id_node = (DCE2_CoCtxIdNode*)DCE2_QueueFirst(cot->pending_ctx_ids); + ctx_id_node != nullptr; + ctx_id_node = (DCE2_CoCtxIdNode*)DCE2_QueueNext(cot->pending_ctx_ids)) + { + if (ctx_id_node->ctx_id == ctx_id) + break; + } + } + + if (ctx_id_node == nullptr) + { + return DCE2_RET__ERROR; + } + } + + if (ctx_id_node->state == DCE2_CO_CTX_STATE__REJECTED) + { + return DCE2_RET__ERROR; + } + + DCE2_CopyUuid(&sd->ropts.iface, &ctx_id_node->iface, DCERPC_BO_FLAG__NONE); + sd->ropts.iface_vers_maj = ctx_id_node->iface_vers_maj; + sd->ropts.iface_vers_min = ctx_id_node->iface_vers_min; + + return DCE2_RET__SUCCESS; +} + +/******************************************************************** + * Function: DCE2_CoSetRopts() + * + * Sets values necessary for the rule options. + * + ********************************************************************/ +static inline void DCE2_CoSetRopts(DCE2_SsnData* sd, DCE2_CoTracker* cot, const + DceRpcCoHdr* co_hdr) +{ + DCE2_CoFragTracker* ft = &cot->frag_tracker; + int opnum = (ft->opnum != DCE2_SENTINEL) ? ft->opnum : cot->opnum; + int ctx_id = (ft->ctx_id != DCE2_SENTINEL) ? ft->ctx_id : cot->ctx_id; + + int data_byte_order = + (cot->data_byte_order != DCE2_SENTINEL) ? + cot->data_byte_order : (int)DceRpcCoByteOrder(co_hdr); + + if (DCE2_CoSetIface(sd, cot, (uint16_t)ctx_id) != DCE2_RET__SUCCESS) + sd->ropts.first_frag = DCE2_SENTINEL; + else + sd->ropts.first_frag = DceRpcCoFirstFrag(co_hdr); + + sd->ropts.hdr_byte_order = DceRpcCoByteOrder(co_hdr); + sd->ropts.data_byte_order = data_byte_order; + sd->ropts.opnum = opnum; + sd->ropts.stub_data = cot->stub_data; +} + +static inline dce2CommonStats* dce_get_proto_stats_ptr(DCE2_SsnData* sd) +{ + if (sd->trans == DCE2_TRANS_TYPE__TCP) + { + return((dce2CommonStats*)&dce2_tcp_stats); + } + else + { + return((dce2CommonStats*)&dce2_smb_stats); + } +} + +/******************************************************************** + * Function: DCE2_CoHdrChecks() + * + * Checks some relevant fields in the header to make sure they're + * sane. + * + ********************************************************************/ +static DCE2_Ret DCE2_CoHdrChecks(DCE2_SsnData* sd, DCE2_CoTracker* cot, const DceRpcCoHdr* co_hdr) +{ + uint16_t frag_len = DceRpcCoFragLen(co_hdr); + DceRpcPduType pdu_type = DceRpcCoPduType(co_hdr); + dce2CommonStats* dce_common_stats = dce_get_proto_stats_ptr(sd); + + if (frag_len < sizeof(DceRpcCoHdr)) + { + /* Assume we autodetected incorrectly or that DCE/RPC is not running + * over the SMB named pipe */ + if (!DCE2_SsnAutodetected(sd) && (sd->trans != DCE2_TRANS_TYPE__SMB)) + { + //FIXIT-M add segment check + dce_alert(GID_DCE2, DCE2_CO_FRAG_LEN_LT_HDR,dce_common_stats); + } + + return DCE2_RET__ERROR; + } + + if (DceRpcCoVersMaj(co_hdr) != DCERPC_PROTO_MAJOR_VERS__5) + { + if (!DCE2_SsnAutodetected(sd) && (sd->trans != DCE2_TRANS_TYPE__SMB)) + { + //FIXIT-M add segment check + dce_alert(GID_DCE2, DCE2_CO_BAD_MAJOR_VERSION,dce_common_stats); + } + + return DCE2_RET__ERROR; + } + + if (DceRpcCoVersMin(co_hdr) != DCERPC_PROTO_MINOR_VERS__0) + { + if (!DCE2_SsnAutodetected(sd) && (sd->trans != DCE2_TRANS_TYPE__SMB)) + { + //FIXIT-M add segment check + dce_alert(GID_DCE2, DCE2_CO_BAD_MINOR_VERSION,dce_common_stats); + } + + return DCE2_RET__ERROR; + } + if (pdu_type >= DCERPC_PDU_TYPE__MAX) + { + if (!DCE2_SsnAutodetected(sd) && (sd->trans != DCE2_TRANS_TYPE__SMB)) + { + //FIXIT-M add segment check + + dce_alert(GID_DCE2, DCE2_CO_BAD_PDU_TYPE,dce_common_stats); + } + + return DCE2_RET__ERROR; + } + + if (DCE2_SsnFromClient(sd->wire_pkt) && (cot->max_xmit_frag != DCE2_SENTINEL)) + { + if (frag_len > cot->max_xmit_frag) + { + //FIXIT-M add segment check + dce_alert(GID_DCE2, DCE2_CO_FRAG_GT_MAX_XMIT_FRAG,dce_common_stats); + } + else if (!DceRpcCoLastFrag(co_hdr) && (pdu_type == DCERPC_PDU_TYPE__REQUEST) + && ((((int)cot->max_xmit_frag - DCE2_MAX_XMIT_SIZE_FUZZ) < 0) + || ((int)frag_len < ((int)cot->max_xmit_frag - DCE2_MAX_XMIT_SIZE_FUZZ)))) + { + /* If client needs to fragment the DCE/RPC request, it shouldn't be less than the + * maximum xmit size negotiated. Only if it's not a last fragment. Make this alert + * only if it is considerably less - have seen legitimate fragments that are just + * slightly less the negotiated fragment size. */ + + //FIXIT-M add segment check + dce_alert(GID_DCE2, DCE2_CO_FRAG_LT_MAX_XMIT_FRAG,dce_common_stats); + } + + /* Continue processing */ + } + + return DCE2_RET__SUCCESS; +} + +/******************************************************************** + * Function: DCE2_CoCtxCompare() + * + * Callback to context id list for finding the right interface + * UUID node. Values passed in are context ids which are used as + * the keys for the list. + * + ********************************************************************/ +static int DCE2_CoCtxCompare(const void* a, const void* b) +{ + int x = (int)(uintptr_t)a; + int y = (int)(uintptr_t)b; + + if (x == y) + return 0; + + /* Only care about equality for finding */ + return -1; +} + +/******************************************************************** + * Function: DCE2_CoCtxFree() + * + * Callback to context id list for freeing context id nodes in + * the list. + * + ********************************************************************/ +static void DCE2_CoCtxFree(void* data) +{ + if (data == nullptr) + return; + + free(data); +} + +/******************************************************************** + * Function: DCE2_CoInitCtxStorage() + * + * Allocates, if necessary, and initializes the context id list + * and the context id pending queue. + * + * + ********************************************************************/ +static inline DCE2_Ret DCE2_CoInitCtxStorage(DCE2_CoTracker* cot) +{ + if (cot == nullptr) + return DCE2_RET__ERROR; + + if (cot->ctx_ids == nullptr) + { + cot->ctx_ids = DCE2_ListNew(DCE2_LIST_TYPE__SPLAYED, DCE2_CoCtxCompare, DCE2_CoCtxFree, + nullptr, DCE2_LIST_FLAG__NO_DUPS); + if (cot->ctx_ids == nullptr) + return DCE2_RET__ERROR; + } + + if (cot->pending_ctx_ids == nullptr) + { + cot->pending_ctx_ids = DCE2_QueueNew(DCE2_CoCtxFree); + if (cot->pending_ctx_ids == nullptr) + { + DCE2_ListDestroy(cot->ctx_ids); + cot->ctx_ids = nullptr; + return DCE2_RET__ERROR; + } + } + else if (!DCE2_QueueIsEmpty(cot->pending_ctx_ids)) + { + DCE2_QueueEmpty(cot->pending_ctx_ids); + } + + return DCE2_RET__SUCCESS; +} + +/******************************************************************** + * Function: DCE2_CoEraseCtxIds() + * + * Empties out the context id list and the pending context id + * queue. Does not free the list and queue - might need to still + * use them. + * + ********************************************************************/ +static inline void DCE2_CoEraseCtxIds(DCE2_CoTracker* cot) +{ + if (cot == nullptr) + return; + + DCE2_QueueEmpty(cot->pending_ctx_ids); + DCE2_ListEmpty(cot->ctx_ids); +} + +DCE2_CoCtxIdNode* dce_process_ctx_id(DCE2_SsnData* sd,DCE2_CoTracker* cot, + const DceRpcCoHdr* co_hdr,DCE2_Policy policy, + const uint8_t* frag_ptr, uint16_t frag_len) +{ + DCE2_CoCtxIdNode* ctx_node; + DCE2_Ret status; + uint16_t ctx_id; + uint8_t num_tsyns; + const Uuid* iface; + uint16_t if_vers_maj; + uint16_t if_vers_min; + DceRpcCoContElem* ctx_elem = (DceRpcCoContElem*)frag_ptr; + dce2CommonStats* dce_common_stats = dce_get_proto_stats_ptr(sd); + + int j; + + if (frag_len < sizeof(DceRpcCoContElem)) + { + dce_alert(GID_DCE2, DCE2_CO_REM_FRAG_LEN_LT_SIZE, dce_common_stats); + return nullptr; + } + + ctx_id = DceRpcCoContElemCtxId(co_hdr, ctx_elem); + num_tsyns = DceRpcCoContElemNumTransSyntaxes(ctx_elem); + iface = DceRpcCoContElemIface(ctx_elem); + if_vers_maj = DceRpcCoContElemIfaceVersMaj(co_hdr, ctx_elem); + if_vers_min = DceRpcCoContElemIfaceVersMin(co_hdr, ctx_elem); + + /* No transfer syntaxes */ + if (num_tsyns == 0) + { + dce_alert(GID_DCE2, DCE2_CO_NO_TFER_SYNTAX_SPECFD,dce_common_stats); + return nullptr; + } + + DCE2_MOVE(frag_ptr, frag_len, sizeof(DceRpcCoContElem)); + + /* Don't really care about the transfer syntaxes */ + for (j = 0; j < num_tsyns; j++) + { + if (frag_len < sizeof(DceRpcCoSynId)) + { + dce_alert(GID_DCE2, DCE2_CO_REM_FRAG_LEN_LT_SIZE, dce_common_stats); + return nullptr; + } + + DCE2_MOVE(frag_ptr, frag_len, sizeof(DceRpcCoSynId)); + } + if (sd->trans == DCE2_TRANS_TYPE__TCP) + { + Profile profile(dce2_tcp_pstat_co_ctx); + } + else + { + Profile profile(dce2_smb_pstat_co_ctx); + } + + /* If there is already an accepted node with in the list + * with this ctx, just return */ + if (policy == DCE2_POLICY__SAMBA_3_0_20) + { + ctx_node = (DCE2_CoCtxIdNode*)DCE2_ListFind(cot->ctx_ids, (void*)(uintptr_t)ctx_id); + if ((ctx_node != nullptr) && (ctx_node->state != DCE2_CO_CTX_STATE__REJECTED)) + { + return nullptr; + } + } + + ctx_node = (DCE2_CoCtxIdNode*)calloc(sizeof(DCE2_CoCtxIdNode),1); + if (ctx_node == nullptr) + { + return nullptr; + } + + /* Add context id to pending queue */ + status = DCE2_QueueEnqueue(cot->pending_ctx_ids, ctx_node); + if (status != DCE2_RET__SUCCESS) + { + free(ctx_node); + return nullptr; + } + + /* This node will get moved to the context id list upon server response */ + ctx_node->ctx_id = ctx_id; + DCE2_CopyUuid(&ctx_node->iface, iface, DceRpcCoByteOrder(co_hdr)); + ctx_node->iface_vers_maj = if_vers_maj; + ctx_node->iface_vers_min = if_vers_min; + ctx_node->state = DCE2_CO_CTX_STATE__PENDING; + return ctx_node; +} + +/******************************************************************** + * Function: DCE2_CoCtxReq() + * + * Handles parsing the context id list out of the packet. + * Context ids and associated uuids are stored in a queue and + * dequeued upon server response. Server response doesn't + * indicate by context id which bindings were accepted or + * rejected, but the index or order they were in in the client + * bind or alter context, hence the queue. + * + ********************************************************************/ +static void DCE2_CoCtxReq(DCE2_SsnData* sd, DCE2_CoTracker* cot, const DceRpcCoHdr* co_hdr, + const uint8_t num_ctx_items, const uint8_t* frag_ptr, uint16_t frag_len) +{ + DCE2_Policy policy = DCE2_SsnGetServerPolicy(sd); + unsigned int i; + dce2CommonStats* dce_common_stats = dce_get_proto_stats_ptr(sd); + + if (num_ctx_items == 0) + { + dce_alert(GID_DCE2, DCE2_CO_NO_CTX_ITEMS_SPECFD, dce_common_stats); + return; + } + + for (i = 0; i < num_ctx_items; i++) + { + DCE2_CoCtxIdNode* ctx_node; + + ctx_node = dce_process_ctx_id(sd,cot,co_hdr,policy,frag_ptr,frag_len); + if ((ctx_node == nullptr)) + { + return; + } + + DebugFormat(DEBUG_DCE_COMMON, "Added Context item to queue.\n" + " Context id: %u\n" + " Interface: %s\n" + " Interface major version: %u\n" + " Interface minor version: %u\n", + ctx_node->ctx_id, + DCE2_UuidToStr(&ctx_node->iface, DCERPC_BO_FLAG__NONE), + ctx_node->iface_vers_maj, ctx_node->iface_vers_min); + + switch (policy) + { + case DCE2_POLICY__SAMBA: + case DCE2_POLICY__SAMBA_3_0_37: + case DCE2_POLICY__SAMBA_3_0_22: + case DCE2_POLICY__SAMBA_3_0_20: + /* Samba only ever looks at one context item. Not sure + * if this is an alertable offense */ + return; + + default: + break; + } + } +} + +void dce_process_ctx_result(DCE2_SsnData* sd,DCE2_CoTracker* cot, + const DceRpcCoHdr* co_hdr,DCE2_Policy policy, + uint16_t result) +{ + DCE2_CoCtxIdNode* ctx_node, * existing_ctx_node; + DCE2_Ret status; + + if (sd->trans == DCE2_TRANS_TYPE__TCP) + { + Profile profile(dce2_tcp_pstat_co_ctx); + } + else + { + Profile profile(dce2_smb_pstat_co_ctx); + } + + /* Dequeue context item in pending queue - this will get put in the permanent + * context id list or free'd */ + ctx_node = (DCE2_CoCtxIdNode*)DCE2_QueueDequeue(cot->pending_ctx_ids); + if (ctx_node == nullptr) + { + LogMessage("%s(%d) Failed to dequeue a context id node.\n", + __FILE__, __LINE__); + return; + } + + DebugFormat(DEBUG_DCE_COMMON, "Adding Context item to context item list.\n" + " Context id: %u\n" + " Interface: %s\n" + " Interface major version: %u\n" + " Interface minor version: %u\n", + ctx_node->ctx_id, + DCE2_UuidToStr(&ctx_node->iface, DCERPC_BO_FLAG__NONE), + ctx_node->iface_vers_maj, ctx_node->iface_vers_min); + + if (result == DCERPC_CO_CONT_DEF_RESULT__ACCEPTANCE) + { + DebugMessage(DEBUG_DCE_COMMON, "Server accepted context item.\n"); + ctx_node->state = DCE2_CO_CTX_STATE__ACCEPTED; + if (DceRpcCoPduType(co_hdr) == DCERPC_PDU_TYPE__BIND_ACK) + cot->got_bind = 1; + } + else + { + DebugMessage(DEBUG_DCE_COMMON, "Server rejected context item.\n"); + ctx_node->state = DCE2_CO_CTX_STATE__REJECTED; + cot->got_bind = 0; + } + + existing_ctx_node = + (DCE2_CoCtxIdNode*)DCE2_ListFind(cot->ctx_ids, (void*)(uintptr_t)ctx_node->ctx_id); + + if (existing_ctx_node != nullptr) + { + switch (policy) + { + case DCE2_POLICY__WIN2000: + case DCE2_POLICY__WIN2003: + case DCE2_POLICY__WINXP: + case DCE2_POLICY__WINVISTA: + case DCE2_POLICY__WIN2008: + case DCE2_POLICY__WIN7: + if (ctx_node->state == DCE2_CO_CTX_STATE__REJECTED) + break; + + if (existing_ctx_node->state == DCE2_CO_CTX_STATE__REJECTED) + { + existing_ctx_node->ctx_id = ctx_node->ctx_id; + DCE2_CopyUuid(&existing_ctx_node->iface, &ctx_node->iface, DCERPC_BO_FLAG__NONE); + existing_ctx_node->iface_vers_maj = ctx_node->iface_vers_maj; + existing_ctx_node->iface_vers_min = ctx_node->iface_vers_min; + existing_ctx_node->state = ctx_node->state; + } + + break; + + case DCE2_POLICY__SAMBA: + case DCE2_POLICY__SAMBA_3_0_37: + case DCE2_POLICY__SAMBA_3_0_22: + case DCE2_POLICY__SAMBA_3_0_20: + /* Samba actually alters the context. Windows keeps the old */ + if (ctx_node->state != DCE2_CO_CTX_STATE__REJECTED) + { + existing_ctx_node->ctx_id = ctx_node->ctx_id; + DCE2_CopyUuid(&existing_ctx_node->iface, &ctx_node->iface, DCERPC_BO_FLAG__NONE); + existing_ctx_node->iface_vers_maj = ctx_node->iface_vers_maj; + existing_ctx_node->iface_vers_min = ctx_node->iface_vers_min; + existing_ctx_node->state = ctx_node->state; + } + + break; + + default: + break; + } + + free((void*)ctx_node); + } + else + { + status = DCE2_ListInsert(cot->ctx_ids, (void*)(uintptr_t)ctx_node->ctx_id, + (void*)ctx_node); + if (status != DCE2_RET__SUCCESS) + { + free((void*)ctx_node); + DebugMessage(DEBUG_DCE_COMMON, + "Failed to add context id node to list.\n"); + return; + } + } +} + +/******************************************************************** + * Function: DCE2_CoBindAck() + * + * Handles the processing of a server bind ack or a server alter + * context response since they share the same header. + * Moves context id items from the pending queue into a list + * ultimately used by the rule options and sets each context item + * as accepted or rejected based on the server response. + * + ********************************************************************/ +static void DCE2_CoBindAck(DCE2_SsnData* sd, DCE2_CoTracker* cot, + const DceRpcCoHdr* co_hdr, const uint8_t* frag_ptr, uint16_t frag_len) +{ + DCE2_Policy policy = DCE2_SsnGetServerPolicy(sd); + DceRpcCoBindAck* bind_ack = (DceRpcCoBindAck*)frag_ptr; + uint16_t sec_addr_len; + const uint8_t* ctx_data; + uint16_t ctx_len; + uint16_t pad = 0; + DceRpcCoContResultList* ctx_list; + uint8_t num_ctx_results; + unsigned int i; + uint16_t max_recv_frag; + dce2CommonStats* dce_common_stats = dce_get_proto_stats_ptr(sd); + + if (frag_len < sizeof(DceRpcCoBindAck)) + { + dce_alert(GID_DCE2, DCE2_CO_REM_FRAG_LEN_LT_SIZE,dce_common_stats); + return; + } + + DCE2_MOVE(frag_ptr, frag_len, sizeof(DceRpcCoBindAck)); + + /* Set what should be the maximum amount of data a client can send in a fragment */ + max_recv_frag = DceRpcCoBindAckMaxRecvFrag(co_hdr, bind_ack); + if ((cot->max_xmit_frag == DCE2_SENTINEL) || (max_recv_frag < cot->max_xmit_frag)) + cot->max_xmit_frag = (int)max_recv_frag; + + sec_addr_len = DceRpcCoSecAddrLen(co_hdr, bind_ack); + + ctx_data = frag_ptr; + ctx_len = frag_len; + + /* First move past secondary address */ + if (ctx_len < sec_addr_len) + { + dce_alert(GID_DCE2, DCE2_CO_REM_FRAG_LEN_LT_SIZE,dce_common_stats); + return; + } + + DCE2_MOVE(ctx_data, ctx_len, sec_addr_len); + + /* padded to 4 octet */ + if ((sizeof(DceRpcCoBindAck) + sec_addr_len) & 3) + pad = (4 - ((sizeof(DceRpcCoBindAck) + sec_addr_len) & 3)); + + if (ctx_len < pad) + { + dce_alert(GID_DCE2, DCE2_CO_REM_FRAG_LEN_LT_SIZE,dce_common_stats); + return; + } + + DCE2_MOVE(ctx_data, ctx_len, pad); + + /* Now we're at the start of the context item results */ + if (ctx_len < sizeof(DceRpcCoContResultList)) + { + dce_alert(GID_DCE2, DCE2_CO_REM_FRAG_LEN_LT_SIZE,dce_common_stats); + return; + } + + ctx_list = (DceRpcCoContResultList*)ctx_data; + num_ctx_results = DceRpcCoContNumResults(ctx_list); + + DCE2_MOVE(ctx_data, ctx_len, sizeof(DceRpcCoContResultList)); + + for (i = 0; i < num_ctx_results; i++) + { + DceRpcCoContResult* ctx_result; + uint16_t result; + + if (ctx_len < sizeof(DceRpcCoContResult)) + { + dce_alert(GID_DCE2, DCE2_CO_REM_FRAG_LEN_LT_SIZE,dce_common_stats); + return; + } + ctx_result = (DceRpcCoContResult*)ctx_data; + result = DceRpcCoContRes(co_hdr, ctx_result); + + DCE2_MOVE(ctx_data, ctx_len, sizeof(DceRpcCoContResult)); + + if (DCE2_QueueIsEmpty(cot->pending_ctx_ids)) + return; + + dce_process_ctx_result(sd,cot,co_hdr,policy,result); + } +} + +/******************************************************************** + * Function: DCE2_CoBind() + * + * Handles the processing of a client bind request. There are + * differences between Windows and Samba and even early Samba in + * how multiple binds on the session are handled. Processing of + * the context id bindings is handed off. + * + ********************************************************************/ +static void DCE2_CoBind(DCE2_SsnData* sd, DCE2_CoTracker* cot, + const DceRpcCoHdr* co_hdr, const uint8_t* frag_ptr, uint16_t frag_len) +{ + DCE2_Policy policy = DCE2_SsnGetServerPolicy(sd); + DceRpcCoBind* bind = (DceRpcCoBind*)frag_ptr; + dce2CommonStats* dce_common_stats = dce_get_proto_stats_ptr(sd); + + if (frag_len < sizeof(DceRpcCoBind)) + { + dce_alert(GID_DCE2, DCE2_CO_REM_FRAG_LEN_LT_SIZE,dce_common_stats); + return; + } + + DCE2_MOVE(frag_ptr, frag_len, sizeof(DceRpcCoBind)); + + switch (policy) + { + case DCE2_POLICY__WIN2000: + case DCE2_POLICY__WIN2003: + case DCE2_POLICY__WINXP: + case DCE2_POLICY__WINVISTA: + case DCE2_POLICY__WIN2008: + case DCE2_POLICY__WIN7: + /* Windows will not accept more than one bind */ + if (!DCE2_ListIsEmpty(cot->ctx_ids)) + { + /* Delete context id list if anything there */ + DCE2_CoEraseCtxIds(cot); + return; + } + + /* Byte order of stub data will be that of the bind */ + cot->data_byte_order = DceRpcCoByteOrder(co_hdr); + + break; + + case DCE2_POLICY__SAMBA: + case DCE2_POLICY__SAMBA_3_0_37: + case DCE2_POLICY__SAMBA_3_0_22: + if (cot->got_bind) + return; + + break; + + case DCE2_POLICY__SAMBA_3_0_20: + /* Accepts multiple binds */ + break; + + default: + LogMessage("%s(%d) Invalid policy: %d\n", + __FILE__, __LINE__, policy); + return; + } + + cot->max_xmit_frag = (int)DceRpcCoBindMaxXmitFrag(co_hdr, bind); + DCE2_CoCtxReq(sd, cot, co_hdr, DceRpcCoNumCtxItems(bind), frag_ptr, frag_len); +} + +/******************************************************************** + * Function: DCE2_CoAlterCtx() + * + * Handles the processing of a client alter context request. + * Again, differences in how this is handled - whether we've seen + * a bind yet or not, altering the data byte order. Processing + * of the context id bindings is handed off. + * + ********************************************************************/ +static void DCE2_CoAlterCtx(DCE2_SsnData* sd, DCE2_CoTracker* cot, + const DceRpcCoHdr* co_hdr, const uint8_t* frag_ptr, uint16_t frag_len) +{ + DCE2_Policy policy = DCE2_SsnGetServerPolicy(sd); + DceRpcCoAltCtx* alt_ctx = (DceRpcCoAltCtx*)frag_ptr; + dce2CommonStats* dce_common_stats = dce_get_proto_stats_ptr(sd); + + if (frag_len < sizeof(DceRpcCoAltCtx)) + { + dce_alert(GID_DCE2, DCE2_CO_REM_FRAG_LEN_LT_SIZE,dce_common_stats); + return; + } + + DCE2_MOVE(frag_ptr, frag_len, sizeof(DceRpcCoAltCtx)); + + switch (policy) + { + case DCE2_POLICY__WIN2000: + case DCE2_POLICY__WIN2003: + case DCE2_POLICY__WINXP: + case DCE2_POLICY__WINVISTA: + case DCE2_POLICY__WIN2008: + case DCE2_POLICY__WIN7: + /* Windows will not accept an alter context before + * bind and will bind_nak it */ + if (DCE2_ListIsEmpty(cot->ctx_ids)) + return; + + if (cot->data_byte_order != (int)DceRpcCoByteOrder(co_hdr)) + { + /* This is anomalous behavior. Alert, but continue processing */ + if (cot->data_byte_order != DCE2_SENTINEL) + dce_alert(GID_DCE2, DCE2_CO_ALTER_CHANGE_BYTE_ORDER,dce_common_stats); + } + + break; + + case DCE2_POLICY__SAMBA: + case DCE2_POLICY__SAMBA_3_0_37: + case DCE2_POLICY__SAMBA_3_0_22: + case DCE2_POLICY__SAMBA_3_0_20: + /* Nothing for Samba */ + break; + + default: + LogMessage("%s(%d) Invalid policy: %d\n", + __FILE__, __LINE__, policy); + break; + } + + /* Alter context is typedef'ed as a bind */ + DCE2_CoCtxReq(sd, cot, co_hdr, DceRpcCoNumCtxItems((DceRpcCoBind*)alt_ctx), frag_ptr, + frag_len); +} + +static int DCE2_CoGetAuthLen(DCE2_SsnData* sd, const DceRpcCoHdr* co_hdr, + const uint8_t* frag_ptr, uint16_t frag_len) +{ + DceRpcCoAuthVerifier* auth_hdr; + uint16_t auth_len = DceRpcCoAuthLen(co_hdr); + dce2CommonStats* dce_common_stats = dce_get_proto_stats_ptr(sd); + + if (auth_len == 0) + return 0; + + auth_len += sizeof(DceRpcCoAuthVerifier); + + /* This means the auth len was bogus */ + if (auth_len > frag_len) + { + dce_alert(GID_DCE2, DCE2_CO_REM_FRAG_LEN_LT_SIZE,dce_common_stats); + return -1; + } + + auth_hdr = (DceRpcCoAuthVerifier*)(frag_ptr + (frag_len - auth_len)); + if (DceRpcCoAuthLevel(auth_hdr) == DCERPC_CO_AUTH_LEVEL__PKT_PRIVACY) + { + /* Data is encrypted - don't inspect */ + return -1; + } + + auth_len += DceRpcCoAuthPad(auth_hdr); + + /* This means the auth pad len was bogus */ + if (auth_len > frag_len) + { + dce_alert(GID_DCE2, DCE2_CO_REM_FRAG_LEN_LT_SIZE,dce_common_stats); + return -1; + } + + return (int)auth_len; +} + +/******************************************************************** + * Function: DCE2_CoRequest() + * + * Handles a DCE/RPC request from the client. This is were the + * client actually asks the server to do stuff on it's behalf. + * If it's a first/last fragment, set relevant rule option + * data and return. If it's a true fragment, do some target + * based futzing to set the right opnum and context id for + * the to be reassembled packet. + * + * + ********************************************************************/ +static void DCE2_CoRequest(DCE2_SsnData* sd, DCE2_CoTracker* cot, + const DceRpcCoHdr* co_hdr, const uint8_t* frag_ptr, uint16_t frag_len) +{ + DceRpcCoRequest* rhdr = (DceRpcCoRequest*)frag_ptr; + uint16_t req_size = sizeof(DceRpcCoRequest); + DCE2_Policy policy = DCE2_SsnGetServerPolicy(sd); + dce2CommonStats* dce_common_stats = dce_get_proto_stats_ptr(sd); + + /* Account for possible object uuid */ + if (DceRpcCoObjectFlag(co_hdr)) + req_size += sizeof(Uuid); + + if (frag_len < req_size) + { + dce_alert(GID_DCE2, DCE2_CO_REM_FRAG_LEN_LT_SIZE,dce_common_stats); + return; + } + + switch (policy) + { + /* After 3.0.37 up to 3.5.2 byte order of stub data is always + * interpreted as little endian */ + case DCE2_POLICY__SAMBA: + cot->data_byte_order = DCERPC_BO_FLAG__LITTLE_ENDIAN; + break; + + case DCE2_POLICY__SAMBA_3_0_37: + case DCE2_POLICY__SAMBA_3_0_22: + case DCE2_POLICY__SAMBA_3_0_20: + cot->data_byte_order = DceRpcCoByteOrder(co_hdr); + break; + + default: + break; + } + + /* Move past header */ + DCE2_MOVE(frag_ptr, frag_len, req_size); + + //FIXIT-M frag stuff + + cot->stub_data = frag_ptr; + cot->opnum = DceRpcCoOpnum(co_hdr, rhdr); + cot->ctx_id = DceRpcCoCtxId(co_hdr, rhdr); + cot->call_id = DceRpcCoCallId(co_hdr); + + if (DceRpcCoFirstFrag(co_hdr) && DceRpcCoLastFrag(co_hdr)) + { + int auth_len = DCE2_CoGetAuthLen(sd, co_hdr, frag_ptr, frag_len); + DebugMessage(DEBUG_DCE_COMMON, "First and last fragment.\n"); + if (auth_len == -1) + return; + DCE2_CoSetRopts(sd, cot, co_hdr); + } + else + { + //FIXIT-M frag stuff + } +} + +/******************************************************************** + * Function: DCE2_CoResponse() + * + * Handles a DCE/RPC response from the server. + * Samba responds to SMB bind write, request write before read with + * a response to the request and doesn't send a bind ack. Get the + * context id from the pending context id list and put in stable + * list. + * + + ********************************************************************/ +static void DCE2_CoResponse(DCE2_SsnData* sd, DCE2_CoTracker* cot, + const DceRpcCoHdr* co_hdr, const uint8_t* frag_ptr, uint16_t frag_len) +{ + DceRpcCoResponse* rhdr = (DceRpcCoResponse*)frag_ptr; + uint16_t ctx_id; + DCE2_Policy policy = DCE2_SsnGetServerPolicy(sd); + dce2CommonStats* dce_common_stats = dce_get_proto_stats_ptr(sd); + + if (frag_len < sizeof(DceRpcCoResponse)) + { + dce_alert(GID_DCE2, DCE2_CO_REM_FRAG_LEN_LT_SIZE,dce_common_stats); + + return; + } + + switch (policy) + { + case DCE2_POLICY__SAMBA: + cot->data_byte_order = DCERPC_BO_FLAG__LITTLE_ENDIAN; + break; + + case DCE2_POLICY__SAMBA_3_0_37: + case DCE2_POLICY__SAMBA_3_0_22: + case DCE2_POLICY__SAMBA_3_0_20: + cot->data_byte_order = DceRpcCoByteOrder(co_hdr); + break; + + default: + break; + } + + ctx_id = DceRpcCoCtxIdResp(co_hdr, rhdr); + + /* If pending queue is not empty, add this context id as accepted and all + * others as pending */ + while (!DCE2_QueueIsEmpty(cot->pending_ctx_ids)) + { + DCE2_Ret status; + DCE2_CoCtxIdNode* ctx_node = (DCE2_CoCtxIdNode*)DCE2_QueueDequeue(cot->pending_ctx_ids); + + if (ctx_node == nullptr) + { + LogMessage("%s(%d) Failed to dequeue a context id node.\n", + __FILE__, __LINE__); + return; + } + + if (ctx_node->ctx_id == ctx_id) + ctx_node->state = DCE2_CO_CTX_STATE__ACCEPTED; + + status = DCE2_ListInsert(cot->ctx_ids, (void*)(uintptr_t)ctx_node->ctx_id, + (void*)ctx_node); + if (status != DCE2_RET__SUCCESS) + { + /* Might be a duplicate in there already. If there is we would have used it + * anyway before looking at the pending queue. Just get rid of it */ + free((void*)ctx_node); + return; + } + } + + /* Move past header */ + DCE2_MOVE(frag_ptr, frag_len, sizeof(DceRpcCoResponse)); + + //FIXIT-M frag stuff + + cot->stub_data = frag_ptr; + /* Opnum not in response header - have to use previous client's */ + cot->ctx_id = ctx_id; + cot->call_id = DceRpcCoCallId(co_hdr); + + if (DceRpcCoFirstFrag(co_hdr) && DceRpcCoLastFrag(co_hdr)) + { + int auth_len = DCE2_CoGetAuthLen(sd, co_hdr, frag_ptr, frag_len); + DebugMessage(DEBUG_DCE_COMMON, "First and last fragment.\n"); + if (auth_len == -1) + return; + DCE2_CoSetRopts(sd, cot, co_hdr); + } + else + { + /* FIXIT-M frag stuff */ + } +} + +/******************************************************************** + * Function: DCE2_CoDecode() + * + * Main processing for the DCE/RPC pdu types. Most are not + * implemented as, currently, they are not necessary and only + * stats are kept for them. Important are the bind, alter context + * and request. + * + ********************************************************************/ +static void DCE2_CoDecode(DCE2_SsnData* sd, DCE2_CoTracker* cot, + const uint8_t* frag_ptr, uint16_t frag_len) +{ + /* Already checked that we have enough data for header */ + const DceRpcCoHdr* co_hdr = (DceRpcCoHdr*)frag_ptr; + int pdu_type = DceRpcCoPduType(co_hdr); + dce2CommonStats* dce_common_stats = dce_get_proto_stats_ptr(sd); + + /* We've got the main header. Move past it to the + * start of the pdu */ + DCE2_MOVE(frag_ptr, frag_len, sizeof(DceRpcCoHdr)); + + DebugMessage(DEBUG_DCE_COMMON, "PDU type: "); + + /* Client specific pdu types - some overlap with server */ + if (DCE2_SsnFromClient(sd->wire_pkt)) + { + switch (pdu_type) + { + case DCERPC_PDU_TYPE__BIND: + DebugMessage(DEBUG_DCE_COMMON, "Bind\n"); + dce_common_stats->co_bind++; + + /* Make sure context id list and queue are initialized */ + if (DCE2_CoInitCtxStorage(cot) != DCE2_RET__SUCCESS) + return; + + DCE2_CoBind(sd, cot, co_hdr, frag_ptr, frag_len); + + break; + + case DCERPC_PDU_TYPE__ALTER_CONTEXT: + DebugMessage(DEBUG_DCE_COMMON, "Alter Context\n"); + dce_common_stats->co_alter_ctx++; + + if (DCE2_CoInitCtxStorage(cot) != DCE2_RET__SUCCESS) + return; + + DCE2_CoAlterCtx(sd, cot, co_hdr, frag_ptr, frag_len); + + break; + + case DCERPC_PDU_TYPE__REQUEST: + DebugMessage(DEBUG_DCE_COMMON, "Request\n"); + dce_common_stats->co_request++; + + if (DCE2_ListIsEmpty(cot->ctx_ids) && + DCE2_QueueIsEmpty(cot->pending_ctx_ids)) + { + return; + } + + DCE2_CoRequest(sd, cot, co_hdr, frag_ptr, frag_len); + + break; + + case DCERPC_PDU_TYPE__AUTH3: + DebugMessage(DEBUG_DCE_COMMON, "Auth3\n"); + dce_common_stats->co_auth3++; + break; + + case DCERPC_PDU_TYPE__CO_CANCEL: + DebugMessage(DEBUG_DCE_COMMON, "Cancel\n"); + dce_common_stats->co_cancel++; + break; + + case DCERPC_PDU_TYPE__ORPHANED: + DebugMessage(DEBUG_DCE_COMMON, "Orphaned\n"); + dce_common_stats->co_orphaned++; + break; + + case DCERPC_PDU_TYPE__MICROSOFT_PROPRIETARY_OUTLOOK2003_RPC_OVER_HTTP: + DebugMessage(DEBUG_DCE_COMMON, "Microsoft Request To Send RPC over HTTP\n"); + dce_common_stats->co_ms_pdu++; + break; + + default: + DebugFormat(DEBUG_DCE_COMMON, "Unknown (0x%02x)\n", pdu_type); + dce_common_stats->co_other_req++; + break; + } + } + else + { + switch (pdu_type) + { + case DCERPC_PDU_TYPE__BIND_ACK: + case DCERPC_PDU_TYPE__ALTER_CONTEXT_RESP: + if (pdu_type == DCERPC_PDU_TYPE__BIND_ACK) + { + DebugMessage(DEBUG_DCE_COMMON, "Bind Ack\n"); + dce_common_stats->co_bind_ack++; + } + else + { + DebugMessage(DEBUG_DCE_COMMON, "Alter Context Response\n"); + dce_common_stats->co_alter_ctx_resp++; + } + + if (DCE2_QueueIsEmpty(cot->pending_ctx_ids)) + return; + + /* Bind ack and alter context response have the same + * header structure, just different pdu type */ + DCE2_CoBindAck(sd, cot, co_hdr, frag_ptr, frag_len); + + /* Got the bind/alter response - clear out the pending queue */ + DCE2_QueueEmpty(cot->pending_ctx_ids); + + break; + + case DCERPC_PDU_TYPE__BIND_NACK: + DebugMessage(DEBUG_DCE_COMMON, "Bind Nack\n"); + dce_common_stats->co_bind_nack++; + + /* Bind nack in Windows seems to blow any previous context away */ + switch (DCE2_SsnGetServerPolicy(sd)) + { + case DCE2_POLICY__WIN2000: + case DCE2_POLICY__WIN2003: + case DCE2_POLICY__WINXP: + case DCE2_POLICY__WINVISTA: + case DCE2_POLICY__WIN2008: + case DCE2_POLICY__WIN7: + DCE2_CoEraseCtxIds(cot); + break; + + default: + break; + } + + cot->got_bind = 0; + + break; + + case DCERPC_PDU_TYPE__RESPONSE: + DebugMessage(DEBUG_DCE_COMMON, "Response\n"); + dce_common_stats->co_response++; + DCE2_CoResponse(sd, cot, co_hdr, frag_ptr, frag_len); + break; + + case DCERPC_PDU_TYPE__FAULT: + DebugMessage(DEBUG_DCE_COMMON, "Fault\n"); + dce_common_stats->co_fault++; + + /* Clear out the client side */ + DCE2_QueueEmpty(cot->pending_ctx_ids); + DCE2_BufferEmpty(cot->cli_seg.buf); + DCE2_BufferEmpty(cot->frag_tracker.cli_stub_buf); + + DCE2_CoResetTracker(cot); + + break; + + case DCERPC_PDU_TYPE__SHUTDOWN: + DebugMessage(DEBUG_DCE_COMMON, "Shutdown\n"); + dce_common_stats->co_shutdown++; + break; + + case DCERPC_PDU_TYPE__REJECT: + DebugMessage(DEBUG_DCE_COMMON, "Reject\n"); + dce_common_stats->co_reject++; + + DCE2_QueueEmpty(cot->pending_ctx_ids); + + break; + + case DCERPC_PDU_TYPE__MICROSOFT_PROPRIETARY_OUTLOOK2003_RPC_OVER_HTTP: + DebugMessage(DEBUG_DCE_COMMON, "Microsoft Request To Send RPC over HTTP\n"); + dce_common_stats->co_ms_pdu++; + break; + + default: + DebugFormat(DEBUG_DCE_COMMON, "Unknown (0x%02x)\n", pdu_type); + dce_common_stats->co_other_resp++; + break; + } + } +} + +/******************************************************************** + * Function: DCE2_CoProcess() + * + * Main entry point for connection-oriented DCE/RPC processing. + * Since there can be more than one DCE/RPC pdu in the packet, it + * loops through the packet data until none is left. It handles + * transport layer segmentation and buffers data until it gets the + * full pdu, then hands off to pdu processing. + * + * + ********************************************************************/ +void DCE2_CoProcess(DCE2_SsnData* sd, DCE2_CoTracker* cot, + const uint8_t* data_ptr, uint16_t data_len) +{ + DCE2_CoSeg* seg = DCE2_CoGetSegPtr(sd, cot); + uint32_t num_frags = 0; + dce2CommonStats* dce_common_stats = dce_get_proto_stats_ptr(sd); + + dce_common_stats->co_pdus++; + + co_reassembled = 0; + + while (data_len > 0) + { + num_frags++; + + DebugFormat(DEBUG_DCE_COMMON, "DCE/RPC message number: %u\n", num_frags); + + /* Fast track full fragments */ + if (DCE2_BufferIsEmpty(seg->buf)) + { + const uint8_t* frag_ptr = data_ptr; + uint16_t frag_len; + + /* Not enough data left for a header. Buffer it and return */ + if (data_len < sizeof(DceRpcCoHdr)) + { + // FIXIT-M add logic for this case + break; + } + + if (DCE2_CoHdrChecks(sd, cot, (DceRpcCoHdr*)data_ptr) != DCE2_RET__SUCCESS) + return; + + frag_len = DceRpcCoFragLen((DceRpcCoHdr*)data_ptr); + + /* Not enough data left for the pdu. */ + if (data_len < frag_len) + { + // FIXIT-M add logic for this case + break; + } + + DCE2_MOVE(data_ptr, data_len, frag_len); + + /* Got a full DCE/RPC pdu */ + DCE2_CoDecode(sd, cot, frag_ptr, frag_len); + + /* If we're configured to do defragmentation only detect on first frag + * since we'll detect on reassembled */ + if (!DCE2_GcDceDefrag((dce2CommonProtoConf*)sd->config) || + ((num_frags == 1) && !co_reassembled)) + DCE2_Detect(sd); + + /* Reset if this is a last frag */ + if (DceRpcCoLastFrag((DceRpcCoHdr*)frag_ptr)) + num_frags = 0; + } + else /* We've already buffered data */ + { + // FIXIT-M add logic for this case + } + } + + // FIXIT-M add reassemble logic +} + diff --git a/src/service_inspectors/dce_rpc/dce_co.h b/src/service_inspectors/dce_rpc/dce_co.h index d97ce9d62..ea1e7775b 100644 --- a/src/service_inspectors/dce_rpc/dce_co.h +++ b/src/service_inspectors/dce_rpc/dce_co.h @@ -24,6 +24,7 @@ #include "dce_common.h" #include "dce_list.h" +#include "dce_utils.h" #define DCE2_CO_BAD_MAJOR_VERSION 27 #define DCE2_CO_BAD_MINOR_VERSION 28 @@ -68,6 +69,8 @@ from opnum established for fragmented request." "Connection-oriented DCE/RPC - Context id of non first/last fragment different \ from context id established for fragmented request." +#define DCE2_MAX_XMIT_SIZE_FUZZ 500 + #pragma pack(1) struct DceRpcCoVersion @@ -88,6 +91,80 @@ struct DceRpcCoHdr uint32_t call_id; }; +/* Bind */ +struct DceRpcCoBind +{ + uint16_t max_xmit_frag; + uint16_t max_recv_frag; + uint32_t assoc_group_id; + uint8_t n_context_elem; /* number of context elements */ + uint8_t reserved; + uint16_t reserved2; +}; + +struct DceRpcCoSynId +{ + Uuid if_uuid; + uint32_t if_version; +}; + +struct DceRpcCoContElem +{ + uint16_t p_cont_id; + uint8_t n_transfer_syn; /* number of transfer syntaxes */ + uint8_t reserved; + DceRpcCoSynId abstract_syntax; +}; + +struct DceRpcCoBindAck +{ + uint16_t max_xmit_frag; + uint16_t max_recv_frag; + uint32_t assoc_group_id; + uint16_t sec_addr_len; +}; + +struct DceRpcCoContResult +{ + uint16_t result; + uint16_t reason; + DceRpcCoSynId transfer_syntax; +}; + +struct DceRpcCoAuthVerifier +{ + uint8_t auth_type; + uint8_t auth_level; + uint8_t auth_pad_length; + uint8_t auth_reserved; + uint32_t auth_context_id; +}; + +struct DceRpcCoRequest +{ + uint32_t alloc_hint; + uint16_t context_id; + uint16_t opnum; +}; + +struct DceRpcCoResponse +{ + uint32_t alloc_hint; + uint16_t context_id; + uint8_t cancel_count; + uint8_t reserved; +}; + +struct DceRpcCoContResultList +{ + uint8_t n_results; + uint8_t reserved; + uint16_t reserved2; +}; + +typedef DceRpcCoBind DceRpcCoAltCtx; +typedef DceRpcCoBindAck DceRpcCoAltCtxResp; + #pragma pack() struct DCE2_CoFragTracker @@ -140,6 +217,56 @@ struct DCE2_CoTracker DCE2_CoSeg srv_seg; }; +/* + * Connection oriented + */ +enum DceRpcCoPfcFlags +{ + DCERPC_CO_PFC_FLAGS__FIRST_FRAG = 0x01, + DCERPC_CO_PFC_FLAGS__LAST_FRAG = 0x02, + DCERPC_CO_PFC_FLAGS__PENDING_CANCEL = 0x04, + DCERPC_CO_PFC_FLAGS__RESERVED_1 = 0x08, + DCERPC_CO_PFC_FLAGS__CONC_MPX = 0x10, + DCERPC_CO_PFC_FLAGS__DID_NOT_EXECUTE = 0x20, + DCERPC_CO_PFC_FLAGS__MAYBE = 0x40, + DCERPC_CO_PFC_FLAGS__OBJECT_UUID = 0x80 +}; + +enum DCE2_CoCtxState +{ + DCE2_CO_CTX_STATE__ACCEPTED, + DCE2_CO_CTX_STATE__REJECTED, + DCE2_CO_CTX_STATE__PENDING +}; + +struct DCE2_CoCtxIdNode +{ + uint16_t ctx_id; /* The context id */ + Uuid iface; /* The presentation syntax uuid for the interface */ + uint16_t iface_vers_maj; /* The major version of the interface */ + uint16_t iface_vers_min; /* The minor version of the interface */ + + /* Whether or not the server accepted or rejected the client bind/alter context + * request. Initially set to pending until server response */ + DCE2_CoCtxState state; +}; + +enum DceRpcCoAuthLevelType +{ + DCERPC_CO_AUTH_LEVEL__NONE = 1, + DCERPC_CO_AUTH_LEVEL__CONNECT, + DCERPC_CO_AUTH_LEVEL__CALL, + DCERPC_CO_AUTH_LEVEL__PKT, + DCERPC_CO_AUTH_LEVEL__PKT_INTEGRITY, + DCERPC_CO_AUTH_LEVEL__PKT_PRIVACY +}; + +enum DceRpcCoContDefResult +{ + DCERPC_CO_CONT_DEF_RESULT__ACCEPTANCE = 0, + DCERPC_CO_CONT_DEF_RESULT__USER_REJECTION, + DCERPC_CO_CONT_DEF_RESULT__PROVIDER_REJECTION +}; inline uint8_t DceRpcCoVersMaj(const DceRpcCoHdr* co) { return co->pversion.major; @@ -165,7 +292,115 @@ inline uint16_t DceRpcCoFragLen(const DceRpcCoHdr* co) return DceRpcNtohs(&co->frag_length, DceRpcCoByteOrder(co)); } +inline uint8_t DceRpcCoNumCtxItems(const DceRpcCoBind* cob) +{ + return cob->n_context_elem; +} + +inline uint16_t DceRpcCoContElemCtxId(const DceRpcCoHdr* co, const DceRpcCoContElem* coce) +{ + return DceRpcNtohs(&coce->p_cont_id, DceRpcCoByteOrder(co)); +} + +inline uint8_t DceRpcCoContElemNumTransSyntaxes(const DceRpcCoContElem* coce) +{ + return coce->n_transfer_syn; +} + +inline const Uuid* DceRpcCoContElemIface(const DceRpcCoContElem* coce) +{ + return &coce->abstract_syntax.if_uuid; +} + +inline uint16_t DceRpcCoContElemIfaceVersMaj(const DceRpcCoHdr* co, const DceRpcCoContElem* coce) +{ + return (uint16_t)(DceRpcNtohl(&coce->abstract_syntax.if_version, DceRpcCoByteOrder(co)) & + 0x0000ffff); +} + +inline uint16_t DceRpcCoContElemIfaceVersMin(const DceRpcCoHdr* co, const DceRpcCoContElem* coce) +{ + return (uint16_t)(DceRpcNtohl(&coce->abstract_syntax.if_version, DceRpcCoByteOrder(co)) >> 16); +} + +inline uint16_t DceRpcCoBindAckMaxRecvFrag(const DceRpcCoHdr* co, const DceRpcCoBindAck* coba) +{ + return DceRpcNtohs(&coba->max_recv_frag, DceRpcCoByteOrder(co)); +} + +inline uint16_t DceRpcCoSecAddrLen(const DceRpcCoHdr* co, const DceRpcCoBindAck* coba) +{ + return DceRpcNtohs(&coba->sec_addr_len, DceRpcCoByteOrder(co)); +} + +inline uint16_t DceRpcCoContRes(const DceRpcCoHdr* co, const DceRpcCoContResult* cocr) +{ + return DceRpcNtohs(&cocr->result, DceRpcCoByteOrder(co)); +} + +inline int DceRpcCoObjectFlag(const DceRpcCoHdr* co) +{ + return co->pfc_flags & DCERPC_CO_PFC_FLAGS__OBJECT_UUID; +} + +inline int DceRpcCoFirstFrag(const DceRpcCoHdr* co) +{ + return co->pfc_flags & DCERPC_CO_PFC_FLAGS__FIRST_FRAG; +} + +inline int DceRpcCoLastFrag(const DceRpcCoHdr* co) +{ + return co->pfc_flags & DCERPC_CO_PFC_FLAGS__LAST_FRAG; +} + +inline uint16_t DceRpcCoAuthLen(const DceRpcCoHdr* co) +{ + return DceRpcNtohs(&co->auth_length, DceRpcCoByteOrder(co)); +} + +inline uint8_t DceRpcCoAuthLevel(const DceRpcCoAuthVerifier* coav) +{ + return coav->auth_level; +} + +inline uint16_t DceRpcCoAuthPad(const DceRpcCoAuthVerifier* coav) +{ + return coav->auth_pad_length; +} + +inline uint16_t DceRpcCoCtxIdResp(const DceRpcCoHdr* co, const DceRpcCoResponse* cor) +{ + return DceRpcNtohs(&cor->context_id, DceRpcCoByteOrder(co)); +} + +inline uint16_t DceRpcCoBindMaxXmitFrag(const DceRpcCoHdr* co, const DceRpcCoBind* cob) +{ + return DceRpcNtohs(&cob->max_xmit_frag, DceRpcCoByteOrder(co)); +} + +inline uint8_t DceRpcCoContNumResults(const DceRpcCoContResultList* cocrl) +{ + return cocrl->n_results; +} + +inline uint32_t DceRpcCoCallId(const DceRpcCoHdr* co) +{ + return DceRpcNtohl(&co->call_id, DceRpcCoByteOrder(co)); +} + +inline uint16_t DceRpcCoOpnum(const DceRpcCoHdr* co, const DceRpcCoRequest* cor) +{ + return DceRpcNtohs(&cor->opnum, DceRpcCoByteOrder(co)); +} + +inline uint16_t DceRpcCoCtxId(const DceRpcCoHdr* co, const DceRpcCoRequest* cor) +{ + return DceRpcNtohs(&cor->context_id, DceRpcCoByteOrder(co)); +} + void DCE2_CoInitTracker(DCE2_CoTracker*); +void DCE2_CoProcess(DCE2_SsnData*, DCE2_CoTracker*, + const uint8_t*, uint16_t); #endif diff --git a/src/service_inspectors/dce_rpc/dce_common.cc b/src/service_inspectors/dce_rpc/dce_common.cc index 9c96365d0..7334dfa15 100644 --- a/src/service_inspectors/dce_rpc/dce_common.cc +++ b/src/service_inspectors/dce_rpc/dce_common.cc @@ -19,11 +19,16 @@ // dce_common.cc author Rashmi Pitre #include "dce_common.h" +#include "dce_tcp.h" +#include "dce_smb.h" #include "framework/base_api.h" #include "framework/module.h" #include "flow/flow.h" #include "log/messages.h" #include "main/snort_debug.h" +#include "detection/detect.h" + +THREAD_LOCAL int dce2_detected = 0; const char* dce2_get_policy_name(DCE2_Policy policy) { @@ -107,7 +112,7 @@ bool dce2_paf_abort(Flow* flow, DCE2_SsnData* sd) return true; } - if ((sd != NULL) && DCE2_SsnNoInspect(sd)) + if ((sd != nullptr) && DCE2_SsnNoInspect(sd)) { DebugMessage(DEBUG_DCE_COMMON, "Aborting PAF because of session data check.\n"); return true; @@ -116,6 +121,78 @@ bool dce2_paf_abort(Flow* flow, DCE2_SsnData* sd) return false; } +void DCE2_PrintRoptions(DCE2_Roptions* ropts) +{ + DebugFormat(DEBUG_DCE_COMMON, + " First frag: %s\n", ropts->first_frag == 1 ? "yes" : (ropts->first_frag == 0 ? "no" : + "unset")); + if (ropts->first_frag == DCE2_SENTINEL) + { + DebugMessage(DEBUG_DCE_COMMON, " Iface: unset\n"); + DebugMessage(DEBUG_DCE_COMMON, " Iface version: unset\n"); + } + else + { + DebugFormat(DEBUG_DCE_COMMON, " Iface: %s\n", DCE2_UuidToStr(&ropts->iface, + DCERPC_BO_FLAG__NONE)); + DebugFormat(DEBUG_DCE_COMMON, " Iface version: %u\n", ropts->iface_vers_maj); + } + if (ropts->opnum == DCE2_SENTINEL) + DebugMessage(DEBUG_DCE_COMMON, " Opnum: unset\n"); + else + DebugFormat(DEBUG_DCE_COMMON, " Opnum: %u\n", ropts->opnum); + DebugFormat(DEBUG_DCE_COMMON, " Header byte order: %s\n", + ropts->hdr_byte_order == DCERPC_BO_FLAG__LITTLE_ENDIAN ? "little endian" : + (ropts->hdr_byte_order == DCERPC_BO_FLAG__BIG_ENDIAN ? "big endian" : "unset")); + DebugFormat(DEBUG_DCE_COMMON, " Data byte order: %s\n", + ropts->data_byte_order == DCERPC_BO_FLAG__LITTLE_ENDIAN ? "little endian" : + (ropts->data_byte_order == DCERPC_BO_FLAG__BIG_ENDIAN ? "big endian" : "unset")); + if (ropts->stub_data != nullptr) + DebugFormat(DEBUG_DCE_COMMON, " Stub data: %p\n", ropts->stub_data); + else + DebugMessage(DEBUG_DCE_COMMON, " Stub data: NULL\n"); +} + +static void dce2_protocol_detect(DCE2_SsnData* sd, Packet* pkt) +{ + if (sd->trans == DCE2_TRANS_TYPE__TCP) + { + Profile profile(dce2_tcp_pstat_detect); + } + else + { + Profile profile(dce2_smb_pstat_detect); + } + SnortEventqPush(); + snort_detect(pkt); + SnortEventqPop(); + + dce2_detected = 1; +} + +void DCE2_Detect(DCE2_SsnData* sd) +{ + Packet* top_pkt = sd->wire_pkt; + //FIXIT-M Get packet from stack + + DebugMessage(DEBUG_DCE_COMMON, "Detecting ------------------------------------------------\n"); + DebugMessage(DEBUG_DCE_COMMON, " Rule options:\n"); + DCE2_PrintRoptions(&sd->ropts); + DebugMessage(DEBUG_DCE_COMMON, "Payload:\n"); + DCE2_PrintPktData(top_pkt->data, top_pkt->dsize); + if (sd->ropts.stub_data != nullptr) + { + DebugMessage(DEBUG_DCE_COMMON,"\nStub data:\n"); + DCE2_PrintPktData(sd->ropts.stub_data, + top_pkt->dsize - (sd->ropts.stub_data - top_pkt->data)); + } + + dce2_protocol_detect(sd, top_pkt); + /* Always reset rule option data after detecting */ + DCE2_ResetRopts(&sd->ropts); + DebugMessage(DEBUG_DCE_COMMON, "----------------------------------------------------------\n"); +} + #ifdef BUILDING_SO extern const BaseApi* ips_dce_iface; diff --git a/src/service_inspectors/dce_rpc/dce_common.h b/src/service_inspectors/dce_rpc/dce_common.h index 4250b656d..236f948b5 100644 --- a/src/service_inspectors/dce_rpc/dce_common.h +++ b/src/service_inspectors/dce_rpc/dce_common.h @@ -26,9 +26,11 @@ #include "framework/module.h" #include "framework/inspector.h" #include "protocols/packet.h" +#include "events/event_queue.h" extern const InspectApi dce2_smb_api; extern const InspectApi dce2_tcp_api; +extern THREAD_LOCAL int dce2_detected; #define GID_DCE2 145 @@ -46,6 +48,40 @@ enum DCE2_Policy DCE2_POLICY__SAMBA_3_0_20, }; +struct dce2CommonStats +{ + PegCount events; + PegCount sessions_aborted; + PegCount bad_autodetects; + + PegCount co_pdus; + PegCount co_bind; + PegCount co_bind_ack; + PegCount co_alter_ctx; + PegCount co_alter_ctx_resp; + PegCount co_bind_nack; + PegCount co_request; + PegCount co_response; + PegCount co_cancel; + PegCount co_orphaned; + PegCount co_fault; + PegCount co_auth3; + PegCount co_shutdown; + PegCount co_reject; + PegCount co_ms_pdu; + PegCount co_other_req; + PegCount co_other_resp; + PegCount co_req_fragments; + PegCount co_resp_fragments; + PegCount co_cli_max_frag_size; + PegCount co_cli_min_frag_size; + PegCount co_cli_seg_reassembled; + PegCount co_cli_frag_reassembled; + PegCount co_srv_max_frag_size; + PegCount co_srv_min_frag_size; + PegCount co_srv_seg_reassembled; + PegCount co_srv_frag_reassembled; +}; #define DCE2_SARG__POLICY_WIN2000 "Win2000" #define DCE2_SARG__POLICY_WINXP "WinXP" #define DCE2_SARG__POLICY_WINVISTA "WinVista" @@ -66,14 +102,6 @@ struct dce2CommonProtoConf #define DCE2_DEBUG__PAF_END_MSG "==========================================================" -/* DCE/RPC byte order flag */ -enum DceRpcBoFlag -{ - DCERPC_BO_FLAG__NONE, - DCERPC_BO_FLAG__BIG_ENDIAN, - DCERPC_BO_FLAG__LITTLE_ENDIAN -}; - enum DceRpcPduType { DCERPC_PDU_TYPE__REQUEST = 0, @@ -151,10 +179,11 @@ struct DCE2_SsnData DCE2_Policy server_policy; DCE2_Policy client_policy; int flags; - const Packet* wire_pkt; + Packet* wire_pkt; uint64_t alert_mask; DCE2_Roptions ropts; int autodetect_dir; + void* config; uint32_t cli_seq; uint32_t cli_nseq; @@ -162,47 +191,13 @@ struct DCE2_SsnData uint32_t srv_nseq; }; -inline DceRpcBoFlag DceRpcByteOrder(const uint8_t value) -{ - if ((value & 0x10) >> 4) - return DCERPC_BO_FLAG__LITTLE_ENDIAN; - - return DCERPC_BO_FLAG__BIG_ENDIAN; -} - -inline uint16_t DceRpcNtohs(const uint16_t* ptr, const DceRpcBoFlag bo_flag) -{ - uint16_t value; - - if (ptr == NULL) - return 0; - -#ifdef WORDS_MUSTALIGN - value = *((uint8_t*)ptr) << 8 | *((uint8_t*)ptr + 1); -#else - value = *ptr; -#endif /* WORDS_MUSTALIGN */ - - if (bo_flag == DCERPC_BO_FLAG__NONE) - return value; - -#ifdef WORDS_BIGENDIAN - if (bo_flag == DCERPC_BO_FLAG__BIG_ENDIAN) -#else - if (bo_flag == DCERPC_BO_FLAG__LITTLE_ENDIAN) -#endif /* WORDS_BIGENDIAN */ - return value; - - return ((value & 0xff00) >> 8) | ((value & 0x00ff) << 8); -} - inline void DCE2_ResetRopts(DCE2_Roptions* ropts) { ropts->first_frag = DCE2_SENTINEL; ropts->opnum = DCE2_SENTINEL; ropts->hdr_byte_order = DCE2_SENTINEL; ropts->data_byte_order = DCE2_SENTINEL; - ropts->stub_data = NULL; + ropts->stub_data = nullptr; } inline void DCE2_SsnSetAutodetected(DCE2_SsnData* sd, Packet* p) @@ -237,9 +232,36 @@ inline int DCE2_SsnNoInspect(DCE2_SsnData* sd) return sd->flags & DCE2_SSN_FLAG__NO_INSPECT; } +inline bool DCE2_GcDceDefrag(dce2CommonProtoConf* config) +{ + return config->disable_defrag; +} + +inline int DCE2_SsnFromServer(Packet* p) +{ + return p->from_server(); +} + +inline int DCE2_SsnFromClient(Packet* p) +{ + return p->from_client(); +} + +inline DCE2_Policy DCE2_SsnGetServerPolicy(DCE2_SsnData* sd) +{ + return sd->server_policy; +} + +inline void dce_alert(uint32_t gid, uint32_t sid, dce2CommonStats* stats) +{ + SnortEventqAdd(gid,sid); + stats->events++; +} + bool dce2_set_common_config(Value&, dce2CommonProtoConf&); void print_dce2_common_config(dce2CommonProtoConf&); bool dce2_paf_abort(Flow*, DCE2_SsnData*); +void DCE2_Detect(DCE2_SsnData*); #endif diff --git a/src/service_inspectors/dce_rpc/dce_list.cc b/src/service_inspectors/dce_rpc/dce_list.cc index bc484f331..55753b177 100644 --- a/src/service_inspectors/dce_rpc/dce_list.cc +++ b/src/service_inspectors/dce_rpc/dce_list.cc @@ -89,6 +89,7 @@ DCE2_List* DCE2_ListNew(DCE2_ListType type, DCE2_ListKeyCompare kc, return list; } + /******************************************************************** * Function: DCE2_ListInsert() * @@ -189,7 +190,6 @@ DCE2_Ret DCE2_ListInsert(DCE2_List* list, void* key, void* data) return DCE2_RET__SUCCESS; } - /******************************************************************** * Function: DCE2_ListFirst() * @@ -262,7 +262,6 @@ void* DCE2_ListNext(DCE2_List* list) return nullptr; } - /******************************************************************** * Function: DCE2_ListEmpty() * @@ -445,3 +444,312 @@ static void DCE2_ListInsertBefore(DCE2_List* list, DCE2_ListNode* insert, DCE2_L list->num_nodes++; } } + +/******************************************************************** + * Function: DCE2_ListFind() + * + * Trys to find a node in the list using key passed in. If list + * is splayed, found node is moved to front of list. The data + * associated with the node is returned. + * + * Arguments: + * DCE2_List * + * A pointer to the list object. + * void * + * Pointer to a key. + * + * Returns: + * void * + * If the key is found, the data associated with the node + * is returned. + * NULL is returned if the item cannot be found given the key. + * + ********************************************************************/ +void* DCE2_ListFind(DCE2_List* list, void* key) +{ + DCE2_ListNode* n; + + if (list == nullptr) + return nullptr; + + for (n = list->head; n != nullptr; n = n->next) + { + int comp = list->compare(key, n->key); + if (comp == 0) + { + /* Found it, break out */ + break; + } + else if ((comp < 0) && (list->type == DCE2_LIST_TYPE__SORTED)) + { + /* Don't look any more if the list is sorted */ + return nullptr; + } + } + + if (n != nullptr) + { + /* If list is splayed, move found node to front of list */ + if ((list->type == DCE2_LIST_TYPE__SPLAYED) && + (n != list->head)) + { + n->prev->next = n->next; + + if (n->next != nullptr) + n->next->prev = n->prev; + else /* it's the tail */ + list->tail = n->prev; + + n->prev = nullptr; + n->next = list->head; + list->head->prev = n; + list->head = n; + } + + return n->data; + } + + return nullptr; +} + +/******************************************************************** + * Function: DCE2_QueueNew() + * + * Creates and initializes a new queue object. + * + * Arguments: + * DCE2_QueueDataFree + * An optional free function for the data inserted into + * the queue. If NULL is passed in, the user will be + * responsible for freeing data left in the queue. + * + * Returns: + * DCE2_Queue * + * Pointer to a new queue object. + * NULL if unable to allocate memory for the object. + * + ********************************************************************/ +DCE2_Queue* DCE2_QueueNew(DCE2_QueueDataFree df) +{ + DCE2_Queue* queue; + + queue = (DCE2_Queue*)SnortAlloc(sizeof(DCE2_Queue)); + if (queue == nullptr) + return nullptr; + + queue->data_free = df; + + return queue; +} + +/******************************************************************** + * Function: DCE2_QueueEnqueue() + * + * Inserts data into the queue. + * + * Arguments: + * DCE2_Queue * + * A pointer to the queue object. + * void * + * Pointer to the data to insert into the queue. + * + * Returns: + * DCE2_Ret + * DCE2_RET__ERROR if memory cannot be allocated for a new + * queue node or the queue object passed in is NULL. + * DCE2_RET__SUCCESS if the data is successfully added to + * the queue. + * + ********************************************************************/ +DCE2_Ret DCE2_QueueEnqueue(DCE2_Queue* queue, void* data) +{ + DCE2_QueueNode* n; + + if (queue == nullptr) + return DCE2_RET__ERROR; + + n = (DCE2_QueueNode*)SnortAlloc(sizeof(DCE2_QueueNode)); + if (n == nullptr) + return DCE2_RET__ERROR; + + n->data = data; + + if (queue->tail == nullptr) + { + queue->head = queue->tail = n; + n->next = nullptr; + } + else + { + queue->tail->next = n; + n->prev = queue->tail; + queue->tail = n; + } + + queue->num_nodes++; + + return DCE2_RET__SUCCESS; +} + +/******************************************************************** + * Function: DCE2_QueueDequeue() + * + * Removes and returns the data in the first node in the queue. + * Note that the user will have to free the data returned. The + * data free function only applies to data that is in the queue + * when it is emptied or destroyed. + * + * Arguments: + * DCE2_Queue * + * A pointer to the queue object. + * + * Returns: + * void * + * The data in the first node in the queue. + * NULL if there are no items in the queue or the queue object + * passed in is NULL. + * + ********************************************************************/ +void* DCE2_QueueDequeue(DCE2_Queue* queue) +{ + DCE2_QueueNode* n; + + if (queue == nullptr) + return nullptr; + + n = queue->head; + + if (n != nullptr) + { + void* data = n->data; + + if (queue->head == queue->tail) + { + queue->head = queue->tail = nullptr; + } + else + { + queue->head->next->prev = nullptr; + queue->head = queue->head->next; + } + + free((void*)n); + + queue->num_nodes--; + + return data; + } + + return nullptr; +} + +/******************************************************************** + * Function: DCE2_QueueEmpty() + * + * Removes all of the nodes in a queue. Does not delete the queue + * object itself. Calls data free function for data if it is + * not NULL. + * + * Arguments: + * DCE2_Queue * + * A pointer to the queue object. + * + * Returns: None + * + ********************************************************************/ +void DCE2_QueueEmpty(DCE2_Queue* queue) +{ + DCE2_QueueNode* n; + + if (queue == nullptr) + return; + + n = queue->head; + + while (n != nullptr) + { + DCE2_QueueNode* tmp = n->next; + + if (queue->data_free != nullptr) + queue->data_free(n->data); + + free((void*)n); + n = tmp; + } + + queue->head = queue->tail = queue->current = nullptr; + queue->num_nodes = 0; +} + +/******************************************************************** + * Function: DCE2_QueueFirst() + * + * Returns a pointer to the data of the first node in the queue. + * Sets a current pointer to the first node in the queue for + * iterating over the queue. + * + * Arguments: + * DCE2_Queue * + * A pointer to the queue object. + * + * Returns: + * void * + * The data in the first node in the queue. + * NULL if the queue object passed in is NULL, or there are + * no items in the queue. + * + ********************************************************************/ +void* DCE2_QueueFirst(DCE2_Queue* queue) +{ + if (queue == nullptr) + return nullptr; + + queue->current = queue->head; + queue->next = nullptr; + + if (queue->current != nullptr) + return queue->current->data; + + return nullptr; +} + +/******************************************************************** + * Function: DCE2_QueueNext() + * + * Increments the current pointer in the queue to the next node in + * the queue and returns the data associated with it. This in + * combination with DCE2_QueueFirst is useful in a for loop to + * iterate over the items in a queue. + * + * Arguments: + * DCE2_Queue * + * A pointer to the queue object. + * + * Returns: + * void * + * The data in the next node in the queue. + * NULL if the queue object passed in is NULL, or we are at + * the end of the queue and there are no next nodes. + * + ********************************************************************/ +void* DCE2_QueueNext(DCE2_Queue* queue) +{ + if (queue == nullptr) + return nullptr; + + if (queue->next != nullptr) + { + queue->current = queue->next; + queue->next = nullptr; + return queue->current->data; + } + else if (queue->current != nullptr) + { + queue->current = queue->current->next; + if (queue->current != nullptr) + return queue->current->data; + } + + return nullptr; +} + diff --git a/src/service_inspectors/dce_rpc/dce_list.h b/src/service_inspectors/dce_rpc/dce_list.h index 18d6e679b..035866b99 100644 --- a/src/service_inspectors/dce_rpc/dce_list.h +++ b/src/service_inspectors/dce_rpc/dce_list.h @@ -113,6 +113,15 @@ void* DCE2_ListNext(DCE2_List*); inline bool DCE2_ListIsEmpty(DCE2_List*); void DCE2_ListEmpty(DCE2_List*); void DCE2_ListDestroy(DCE2_List*); +void* DCE2_ListFind(DCE2_List*, void*); + +DCE2_Queue* DCE2_QueueNew(DCE2_QueueDataFree); +DCE2_Ret DCE2_QueueEnqueue(DCE2_Queue*, void*); +void* DCE2_QueueDequeue(DCE2_Queue*); +static inline bool DCE2_QueueIsEmpty(DCE2_Queue*); +void DCE2_QueueEmpty(DCE2_Queue*); +void* DCE2_QueueFirst(DCE2_Queue*); +void* DCE2_QueueNext(DCE2_Queue*); /******************************************************************** * Function: DCE2_ListIsEmpty() @@ -140,5 +149,21 @@ inline bool DCE2_ListIsEmpty(DCE2_List* list) return false; } +/******************************************************************** + * Function: DCE2_QueueIsEmpty() + * + * Determines whether or not the queue has any items in it + * currently. + * + ********************************************************************/ +inline bool DCE2_QueueIsEmpty(DCE2_Queue* queue) +{ + if (queue == nullptr) + return 1; + if (queue->num_nodes == 0) + return 1; + return 0; +} + #endif diff --git a/src/service_inspectors/dce_rpc/dce_smb.cc b/src/service_inspectors/dce_rpc/dce_smb.cc index efb931866..fc71b836f 100644 --- a/src/service_inspectors/dce_rpc/dce_smb.cc +++ b/src/service_inspectors/dce_rpc/dce_smb.cc @@ -69,11 +69,14 @@ static DCE2_SmbSsnData* set_new_dce2_smb_session(Packet* p) return(&fd->dce2_smb_session); } -static DCE2_SmbSsnData* dce2_create_new_smb_session(Packet* p, dce2SmbProtoConf config) +static DCE2_SmbSsnData* dce2_create_new_smb_session(Packet* p, dce2SmbProtoConf* config) { - DCE2_SmbSsnData* dce2_smb_sess = NULL; + DCE2_SmbSsnData* dce2_smb_sess = nullptr; Profile profile(dce2_smb_pstat_new_session); + //FIXIT-M Re-evaluate after infrastructure/binder support if autodetect here + //is necessary + if (DCE2_SmbAutodetect(p)) { DebugMessage(DEBUG_DCE_SMB, "DCE over SMB packet detected\n"); @@ -99,9 +102,10 @@ static DCE2_SmbSsnData* dce2_create_new_smb_session(Packet* p, dce2SmbProtoConf DebugFormat(DEBUG_DCE_SMB,"Created (%p)\n", (void*)dce2_smb_sess); dce2_smb_sess->sd.trans = DCE2_TRANS_TYPE__SMB; - dce2_smb_sess->sd.server_policy = config.common.policy; + dce2_smb_sess->sd.server_policy = config->common.policy; dce2_smb_sess->sd.client_policy = DCE2_POLICY__WINXP; dce2_smb_sess->sd.wire_pkt = p; + dce2_smb_sess->sd.config = (void*)config; DCE2_SsnSetAutodetected(&dce2_smb_sess->sd, p); } @@ -110,7 +114,7 @@ static DCE2_SmbSsnData* dce2_create_new_smb_session(Packet* p, dce2SmbProtoConf return dce2_smb_sess; } -DCE2_SmbSsnData* dce2_handle_smb_session(Packet* p, dce2SmbProtoConf& config) +DCE2_SmbSsnData* dce2_handle_smb_session(Packet* p, dce2SmbProtoConf* config) { Profile profile(dce2_smb_pstat_session); @@ -134,7 +138,7 @@ DCE2_SmbSsnData* dce2_handle_smb_session(Packet* p, dce2SmbProtoConf& config) DCE2_SsnNoInspect(sd); dce2_smb_stats.sessions_aborted++; dce2_smb_stats.bad_autodetects++; - return NULL; + return nullptr; } DCE2_SsnClearAutodetected(sd); } @@ -200,7 +204,7 @@ void Dce2Smb::eval(Packet* p) return; } - dce2_smb_sess = dce2_handle_smb_session(p, config); + dce2_smb_sess = dce2_handle_smb_session(p, &config); if (!dce2_smb_sess) { return; diff --git a/src/service_inspectors/dce_rpc/dce_smb.h b/src/service_inspectors/dce_rpc/dce_smb.h index 996c218df..f93ecdc18 100644 --- a/src/service_inspectors/dce_rpc/dce_smb.h +++ b/src/service_inspectors/dce_rpc/dce_smb.h @@ -133,9 +133,6 @@ struct dce2SmbStats PegCount sessions_aborted; PegCount bad_autodetects; - PegCount smb_sessions; - PegCount smb_pkts; - PegCount co_pdus; PegCount co_bind; PegCount co_bind_ack; @@ -150,6 +147,7 @@ struct dce2SmbStats PegCount co_auth3; PegCount co_shutdown; PegCount co_reject; + PegCount co_ms_pdu; PegCount co_other_req; PegCount co_other_resp; PegCount co_req_fragments; @@ -163,6 +161,8 @@ struct dce2SmbStats PegCount co_srv_seg_reassembled; PegCount co_srv_frag_reassembled; + PegCount smb_sessions; + PegCount smb_pkts; PegCount smb_ignored_bytes; PegCount smb_cli_seg_reassembled; PegCount smb_srv_seg_reassembled; @@ -417,7 +417,7 @@ struct DCE2_SmbSsnData // For tracking requests / responses DCE2_SmbRequestTracker rtracker; - //DCE2_Queue *rtrackers; + DCE2_Queue* rtrackers; uint16_t max_outstanding_requests; uint16_t outstanding_requests; diff --git a/src/service_inspectors/dce_rpc/dce_smb_module.cc b/src/service_inspectors/dce_rpc/dce_smb_module.cc index 4b002056d..114ca72b3 100644 --- a/src/service_inspectors/dce_rpc/dce_smb_module.cc +++ b/src/service_inspectors/dce_rpc/dce_smb_module.cc @@ -32,47 +32,48 @@ static const PegInfo dce2_smb_pegs[] = { "events", "total events" }, { "aborted sessions", "total aborted sessions" }, { "bad autodetects", "total bad autodetects" }, - { "smb sessions", "total smb sessions" }, - { "smb packets", "total smb packets" }, - { "connection-oriented PDUs", "total connection-oriented PDUs" }, - { "connection-oriented binds", "total connection-oriented binds" }, - { "connection-oriented bind acks", "total connection-oriented binds acks" }, - { "connection-oriented alter contexts", "total connection-oriented alter contexts" }, - { "connection-oriented alter context responses", + { "PDUs", "total connection-oriented PDUs" }, + { "Binds", "total connection-oriented binds" }, + { "Bind acks", "total connection-oriented binds acks" }, + { "Alter contexts", "total connection-oriented alter contexts" }, + { "Alter context responses", "total connection-oriented alter context responses" }, - { "connection-oriented bind naks", "total connection-oriented bind naks" }, - { "connection-oriented requests", "total connection-oriented requests" }, - { "connection-oriented responses", "total connection-oriented responses" }, - { "connection-oriented cancels", "total connection-oriented cancels" }, - { "connection-oriented orphaned", "total connection-oriented orphaned" }, - { "connection-oriented faults", "total connection-oriented faults" }, - { "connection-oriented auth3s", "total connection-oriented auth3s" }, - { "connection-oriented shutdowns", "total connection-oriented shutdowns" }, - { "connection-oriented rejects", "total connection-oriented rejects" }, - { "connection-oriented other requests", "total connection-oriented other requests" }, - { "connection-oriented other responses", "total connection-oriented other responses" }, - { "connection-oriented request fragments", "total connection-oriented request fragments" }, - { "connection-oriented response fragments", "total connection-oriented response fragments" }, - { "connection-oriented client maximum fragment size", + { "Bind naks", "total connection-oriented bind naks" }, + { "Requests", "total connection-oriented requests" }, + { "Responses", "total connection-oriented responses" }, + { "Cancels", "total connection-oriented cancels" }, + { "Orphaned", "total connection-oriented orphaned" }, + { "Faults", "total connection-oriented faults" }, + { "Auth3s", "total connection-oriented auth3s" }, + { "Shutdowns", "total connection-oriented shutdowns" }, + { "Rejects", "total connection-oriented rejects" }, + { "MS RPC/HTTP PDUs", "total connection-oriented MS requests to send RPC over HTTP" }, + { "Other requests", "total connection-oriented other requests" }, + { "Other responses", "total connection-oriented other responses" }, + { "Request fragments", "total connection-oriented request fragments" }, + { "Response fragments", "total connection-oriented response fragments" }, + { "Client max fragment size", "connection-oriented client maximum fragment size" }, - { "connection-oriented client minimum fragment size", + { "Client min fragment size", "connection-oriented client minimum fragment size" }, - { "connection-oriented client segments reassembled", + { "Client segs reassembled", "total connection-oriented client segments reassembled" }, - { "connection-oriented client fragments reassembled", + { "Client frags reassembled", "total connection-oriented client fragments reassembled" }, - { "connection-oriented server maximum fragment size", + { "Server max fragment size", "connection-oriented server maximum fragment size" }, - { "connection-oriented server minimum fragment size", + { "Server min fragment size", "connection-oriented server minimum fragment size" }, - { "connection-oriented server segments reassembled", + { "Server segs reassembled", "total connection-oriented server segments reassembled" }, - { "connection-oriented server fragments reassembled", + { "Server frags reassembled", "total connection-oriented server fragments reassembled" }, - { "smb client segments reassembled", "total smb client segments reassembled" }, - { "smb server segments reassembled", "total smb server segments reassembled" }, - { "smb maximum outstanding requests", "total smb maximum outstanding requests" }, - { "smb files processed", "total smb files processed" }, + { "Sessions", "total smb sessions" }, + { "Packets", "total smb packets" }, + { "Client segs reassembled", "total smb client segments reassembled" }, + { "Server segs reassembled", "total smb server segments reassembled" }, + { "Max outstanding requests", "total smb maximum outstanding requests" }, + { "Files processed", "total smb files processed" }, { nullptr, nullptr } }; @@ -202,99 +203,99 @@ ProfileStats* Dce2SmbModule::get_profile( switch ( index ) { case 0: - name = "dce smb main"; + name = "dce_smb_main"; parent = nullptr; return &dce2_smb_pstat_main; case 1: - name = "dce smb session"; - parent = "dce smb main"; + name = "dce_smb_session"; + parent = "dce_smb_main"; return &dce2_smb_pstat_session; case 2: - name = "dce smb new_session"; - parent = "dce smb session"; + name = "dce_smb_new_session"; + parent = "dce_smb_session"; return &dce2_smb_pstat_new_session; case 3: - name = "dce smb detect"; - parent = "dce smb main"; + name = "dce_smb_detect"; + parent = "dce_smb_main"; return &dce2_smb_pstat_detect; case 4: - name = "dce smb log"; - parent = "dce smb main"; + name = "dce_smb_log"; + parent = "dce_smb_main"; return &dce2_smb_pstat_log; case 5: - name = "dce smb connection-oriented segment"; - parent = "dce smb main"; + name = "dce_smb_co_segment"; + parent = "dce_smb_main"; return &dce2_smb_pstat_co_seg; case 6: - name = "dce smb connection-oriented fragment"; - parent = "dce smb main"; + name = "dce_smb_co_fragment"; + parent = "dce_smb_main"; return &dce2_smb_pstat_co_frag; case 7: - name = "dce smb connection-oriented reassembly"; - parent = "dce smb main"; + name = "dce_smb_co_reassembly"; + parent = "dce_smb_main"; return &dce2_smb_pstat_co_reass; case 8: - name = "dce smb connection-oriented context"; - parent = "dce smb main"; + name = "dce_smb_co_context"; + parent = "dce_smb_main"; return &dce2_smb_pstat_co_ctx; case 9: - name = "dce smb segment"; - parent = "dce smb main"; + name = "dce_smb_segment"; + parent = "dce_smb_main"; return &dce2_smb_pstat_smb_seg; case 10: - name = "dce smb request"; - parent = "dce smb main"; + name = "dce_smb_request"; + parent = "dce_smb_main"; return &dce2_smb_pstat_smb_req; case 11: - name = "dce smb uid"; - parent = "dce smb main"; + name = "dce_smb_uid"; + parent = "dce_smb_main"; return &dce2_smb_pstat_smb_uid; case 12: - name = "dce smb tid"; - parent = "dce smb main"; + name = "dce_smb_tid"; + parent = "dce_smb_main"; return &dce2_smb_pstat_smb_tid; case 13: - name = "dce smb fid"; - parent = "dce smb main"; + name = "dce_smb_fid"; + parent = "dce_smb_main"; return &dce2_smb_pstat_smb_fid; case 14: - name = "dce smb file"; - parent = "dce smb main"; + name = "dce_smb_file"; + parent = "dce_smb_main"; return &dce2_smb_pstat_smb_file; case 15: - name = "dce smb file detect"; - parent = "dce smb file"; + name = "dce_smb_file_detect"; + parent = "dce_smb_file"; return &dce2_smb_pstat_smb_file_detect; case 16: - name = "dce smb file api"; - parent = "dce smb file"; + name = "dce_smb_file_api"; + parent = "dce_smb_file"; return &dce2_smb_pstat_smb_file_api; case 17: - name = "dce smb fingerprint"; - parent = "dce smb main"; + name = "dce_smb_fingerprint"; + parent = "dce_smb_main"; return &dce2_smb_pstat_smb_fingerprint; case 18: - name = "dce smb negotiate"; - parent = "dce smb main"; + name = "dce_smb_negotiate"; + parent = "dce_smb_main"; return &dce2_smb_pstat_smb_negotiate; } return nullptr; @@ -305,7 +306,7 @@ static int smb_invalid_share_compare(const void* a, const void* b) dce2SmbShare* ashare = (dce2SmbShare*)a; dce2SmbShare* bshare = (dce2SmbShare*)b; - if ((ashare == NULL) || (bshare == NULL)) + if ((ashare == nullptr) || (bshare == nullptr)) return -1; /* Just check the ascii string */ @@ -323,7 +324,7 @@ static void smb_invalid_share_free(void* data) { dce2SmbShare* smb_share = (dce2SmbShare*)data; - if (smb_share == NULL) + if (smb_share == nullptr) return; free(smb_share->unicode_str); @@ -373,7 +374,7 @@ static bool set_smb_invalid_shares(dce2SmbProtoConf& config, Value& v) smb_share = (dce2SmbShare*)calloc(sizeof(dce2SmbShare),1); smb_share_key = (dce2SmbShare*)calloc(sizeof(dce2SmbShare),1); - if ((smb_share == NULL) || (smb_share_key == NULL)) + if ((smb_share == nullptr) || (smb_share_key == nullptr)) { FatalError("DCE2 - Could not allocate memory for config\n"); } @@ -384,7 +385,7 @@ static bool set_smb_invalid_shares(dce2SmbProtoConf& config, Value& v) smb_share->ascii_str_len = share_len + 1; smb_share->ascii_str = (char*)calloc(smb_share->ascii_str_len,1); - if ((smb_share->unicode_str == NULL) || (smb_share->ascii_str == NULL)) + if ((smb_share->unicode_str == nullptr) || (smb_share->ascii_str == nullptr)) { FatalError("DCE2 - Could not allocate memory for config\n"); } @@ -399,7 +400,7 @@ static bool set_smb_invalid_shares(dce2SmbProtoConf& config, Value& v) smb_share_key->ascii_str_len = smb_share->ascii_str_len; smb_share_key->ascii_str = (char*)calloc(smb_share_key->ascii_str_len,1); - if (smb_share_key->ascii_str == NULL) + if (smb_share_key->ascii_str == nullptr) { FatalError("DCE2 - Could not allocate memory for config\n"); } @@ -424,7 +425,7 @@ static bool set_smb_invalid_shares(dce2SmbProtoConf& config, Value& v) if (error) { DCE2_ListDestroy(config.smb_invalid_shares); - config.smb_invalid_shares = NULL; + config.smb_invalid_shares = nullptr; return error; } @@ -517,14 +518,14 @@ void print_dce2_smb_conf(dce2SmbProtoConf& config) { LogMessage(" SMB valid versions : all\n"); } - if (config.smb_invalid_shares != NULL) + if (config.smb_invalid_shares != nullptr) { dce2SmbShare* share; LogMessage(" Invalid SMB shares:\n"); for (share = (dce2SmbShare*)DCE2_ListFirst(config.smb_invalid_shares); - share != NULL; + share != nullptr; share = (dce2SmbShare*)DCE2_ListNext(config.smb_invalid_shares)) { LogMessage(" %s\n",share->ascii_str); diff --git a/src/service_inspectors/dce_rpc/dce_smb_paf.cc b/src/service_inspectors/dce_rpc/dce_smb_paf.cc index 160f07224..60c65a752 100644 --- a/src/service_inspectors/dce_rpc/dce_smb_paf.cc +++ b/src/service_inspectors/dce_rpc/dce_smb_paf.cc @@ -110,7 +110,7 @@ StreamSplitter::Status dce2_smb_paf(DCE2_PafSmbData* ss, Flow* flow, const uint8 while (n < len) { - DebugFormat(DEBUG_DCE_SMB, " State %d : 0x%02x", ss->paf_state, data[n]); + Debug::print(nullptr, 0, DEBUG_DCE_SMB, " State %d : 0x%02x\n", ss->paf_state, data[n]); switch (ss->paf_state) { @@ -131,7 +131,7 @@ StreamSplitter::Status dce2_smb_paf(DCE2_PafSmbData* ss, Flow* flow, const uint8 DebugFormat(DEBUG_DCE_SMB, "%s\n", DCE2_DEBUG__PAF_END_MSG); return StreamSplitter::FLUSH; } - DebugMessage(DEBUG_DCE_SMB, "Invalid NetBIOS header - " + Debug::print(nullptr, 0, DEBUG_DCE_SMB, "%s", "Invalid NetBIOS header - " "entering junk data states.\n"); ss->paf_state = (DCE2_PafSmbStates)(((int)ss->paf_state) + 1); break; @@ -140,14 +140,14 @@ StreamSplitter::Status dce2_smb_paf(DCE2_PafSmbData* ss, Flow* flow, const uint8 if (!DCE2_PafSmbIsValidNetbiosHdr((uint32_t)(ss->nb_hdr >> 32), true)) { - DebugMessage(DEBUG_DCE_SMB, "Invalid NetBIOS header - " + Debug::print(nullptr, 0, DEBUG_DCE_SMB, "%s", "Invalid NetBIOS header - " "staying in State 7.\n"); break; } if (((uint32_t)ss->nb_hdr != DCE2_SMB_ID) && ((uint32_t)ss->nb_hdr != DCE2_SMB2_ID)) { - DebugMessage(DEBUG_DCE_SMB, "Invalid SMB ID - " + Debug::print(nullptr, 0, DEBUG_DCE_SMB, "%s", "Invalid SMB ID - " "staying in State 7.\n"); break; } diff --git a/src/service_inspectors/dce_rpc/dce_tcp.cc b/src/service_inspectors/dce_rpc/dce_tcp.cc index e0a371eea..87033377f 100644 --- a/src/service_inspectors/dce_rpc/dce_tcp.cc +++ b/src/service_inspectors/dce_rpc/dce_tcp.cc @@ -22,7 +22,9 @@ #include "dce_tcp.h" #include "dce_tcp_paf.h" #include "dce_tcp_module.h" +#include "dce_co.h" #include "main/snort_debug.h" +#include "detection/detect.h" Dce2TcpFlowData::Dce2TcpFlowData() : FlowData(flow_id) { @@ -58,11 +60,13 @@ static DCE2_TcpSsnData* set_new_dce2_tcp_session(Packet* p) return(&fd->dce2_tcp_session); } -static DCE2_TcpSsnData* dce2_create_new_tcp_session(Packet* p, dce2TcpProtoConf config) +static DCE2_TcpSsnData* dce2_create_new_tcp_session(Packet* p, dce2TcpProtoConf* config) { - DCE2_TcpSsnData* dce2_tcp_sess = NULL; + DCE2_TcpSsnData* dce2_tcp_sess = nullptr; Profile profile(dce2_tcp_pstat_new_session); + //FIXIT-M Re-evaluate after infrastructure/binder support if autodetect here + //is necessary if (DCE2_TcpAutodetect(p)) { DebugMessage(DEBUG_DCE_TCP, "DCE over TCP packet detected\n"); @@ -79,9 +83,10 @@ static DCE2_TcpSsnData* dce2_create_new_tcp_session(Packet* p, dce2TcpProtoConf DebugFormat(DEBUG_DCE_TCP,"Created (%p)\n", (void*)dce2_tcp_sess); dce2_tcp_sess->sd.trans = DCE2_TRANS_TYPE__TCP; - dce2_tcp_sess->sd.server_policy = config.common.policy; + dce2_tcp_sess->sd.server_policy = config->common.policy; dce2_tcp_sess->sd.client_policy = DCE2_POLICY__WINXP; dce2_tcp_sess->sd.wire_pkt = p; + dce2_tcp_sess->sd.config = (void*)config; DCE2_SsnSetAutodetected(&dce2_tcp_sess->sd, p); } @@ -90,7 +95,7 @@ static DCE2_TcpSsnData* dce2_create_new_tcp_session(Packet* p, dce2TcpProtoConf return dce2_tcp_sess; } -DCE2_TcpSsnData* dce2_handle_tcp_session(Packet* p, dce2TcpProtoConf& config) +DCE2_TcpSsnData* dce2_handle_tcp_session(Packet* p, dce2TcpProtoConf* config) { Profile profile(dce2_tcp_pstat_session); @@ -114,7 +119,7 @@ DCE2_TcpSsnData* dce2_handle_tcp_session(Packet* p, dce2TcpProtoConf& config) DCE2_SsnNoInspect(sd); dce2_tcp_stats.sessions_aborted++; dce2_tcp_stats.bad_autodetects++; - return NULL; + return nullptr; } DCE2_SsnClearAutodetected(sd); @@ -122,8 +127,13 @@ DCE2_TcpSsnData* dce2_handle_tcp_session(Packet* p, dce2TcpProtoConf& config) } DebugFormat(DEBUG_DCE_TCP, "Session pointer: %p\n", (void*)dce2_tcp_sess); + if (dce2_tcp_sess) + { + //FIXIT-M Stack push - // FIXIT-M add remaining session handling logic + p->packet_flags |= PKT_ALLOW_MULTIPLE_DETECT; + dce2_detected = 0; + } return dce2_tcp_sess; } @@ -162,6 +172,14 @@ void Dce2Tcp::eval(Packet* p) { DCE2_TcpSsnData* dce2_tcp_sess; Profile profile(dce2_tcp_pstat_main); + if (DCE2_SsnFromServer(p)) + { + DebugMessage(DEBUG_DCE_TCP, "Packet from Server.\n"); + } + else + { + DebugMessage(DEBUG_DCE_TCP, "Packet from Client.\n"); + } assert(p->has_tcp_data()); assert(p->flow); @@ -173,12 +191,22 @@ void Dce2Tcp::eval(Packet* p) return; } - dce2_tcp_sess = dce2_handle_tcp_session(p, config); - if (!dce2_tcp_sess) + dce2_tcp_sess = dce2_handle_tcp_session(p, &config); + if (dce2_tcp_sess) { - return; + dce2_tcp_stats.tcp_pkts++; + DCE2_CoProcess(&dce2_tcp_sess->sd, &dce2_tcp_sess->co_tracker, p->data, + p->dsize); + + if (!dce2_detected) + DCE2_Detect(&dce2_tcp_sess->sd); + + DCE2_ResetRopts(&dce2_tcp_sess->sd.ropts); + //FIXIT-M DCE2_PopPkt(sd); + + if (!DCE2_SsnAutodetected(&dce2_tcp_sess->sd)) + DisableInspection(); } - dce2_tcp_stats.tcp_pkts++; } //------------------------------------------------------------------------- diff --git a/src/service_inspectors/dce_rpc/dce_tcp.h b/src/service_inspectors/dce_rpc/dce_tcp.h index 4f63d5a79..7c956d99b 100644 --- a/src/service_inspectors/dce_rpc/dce_tcp.h +++ b/src/service_inspectors/dce_rpc/dce_tcp.h @@ -38,13 +38,14 @@ struct dce2TcpStats PegCount autoports[65535][DCE2_TRANS_TYPE__MAX]; #endif */ + /* The common stats block has to be at the beginning followed + by the protocol specific stats */ + + /*common stats -defined in common.h*/ PegCount events; PegCount sessions_aborted; PegCount bad_autodetects; - PegCount tcp_sessions; - PegCount tcp_pkts; - PegCount co_pdus; PegCount co_bind; PegCount co_bind_ack; @@ -59,6 +60,7 @@ struct dce2TcpStats PegCount co_auth3; PegCount co_shutdown; PegCount co_reject; + PegCount co_ms_pdu; PegCount co_other_req; PegCount co_other_resp; PegCount co_req_fragments; @@ -71,6 +73,10 @@ struct dce2TcpStats PegCount co_srv_min_frag_size; PegCount co_srv_seg_reassembled; PegCount co_srv_frag_reassembled; + + /*DCE TCP specific*/ + PegCount tcp_sessions; + PegCount tcp_pkts; }; extern THREAD_LOCAL dce2TcpStats dce2_tcp_stats; @@ -96,7 +102,7 @@ inline bool DCE2_TcpAutodetect(Packet* p) && (DceRpcCoVersMin(co_hdr) == DCERPC_PROTO_MINOR_VERS__0) && ((p->from_client() && DceRpcCoPduType(co_hdr) == DCERPC_PDU_TYPE__BIND) - || (p->from_server() + || (DCE2_SsnFromServer(p) && DceRpcCoPduType(co_hdr) == DCERPC_PDU_TYPE__BIND_ACK)) && (DceRpcCoFragLen(co_hdr) >= sizeof(DceRpcCoHdr))) { diff --git a/src/service_inspectors/dce_rpc/dce_tcp_module.cc b/src/service_inspectors/dce_rpc/dce_tcp_module.cc index ccf5ad9a4..3ffbe5aa2 100644 --- a/src/service_inspectors/dce_rpc/dce_tcp_module.cc +++ b/src/service_inspectors/dce_rpc/dce_tcp_module.cc @@ -63,43 +63,44 @@ static const PegInfo dce2_tcp_pegs[] = { "events", "total events" }, { "aborted sessions", "total aborted sessions" }, { "bad autodetects", "total bad autodetects" }, - { "tcp sessions", "total tcp sessions" }, - { "tcp packets", "total tcp packets" }, - { "connection-oriented PDUs", "total connection-oriented PDUs" }, - { "connection-oriented binds", "total connection-oriented binds" }, - { "connection-oriented bind acks", "total connection-oriented binds acks" }, - { "connection-oriented alter contexts", "total connection-oriented alter contexts" }, - { "connection-oriented alter context responses", + { "PDUs", "total connection-oriented PDUs" }, + { "Binds", "total connection-oriented binds" }, + { "Bind acks", "total connection-oriented binds acks" }, + { "Alter contexts", "total connection-oriented alter contexts" }, + { "Alter context responses", "total connection-oriented alter context responses" }, - { "connection-oriented bind naks", "total connection-oriented bind naks" }, - { "connection-oriented requests", "total connection-oriented requests" }, - { "connection-oriented responses", "total connection-oriented responses" }, - { "connection-oriented cancels", "total connection-oriented cancels" }, - { "connection-oriented orphaned", "total connection-oriented orphaned" }, - { "connection-oriented faults", "total connection-oriented faults" }, - { "connection-oriented auth3s", "total connection-oriented auth3s" }, - { "connection-oriented shutdowns", "total connection-oriented shutdowns" }, - { "connection-oriented rejects", "total connection-oriented rejects" }, - { "connection-oriented other requests", "total connection-oriented other requests" }, - { "connection-oriented other responses", "total connection-oriented other responses" }, - { "connection-oriented request fragments", "total connection-oriented request fragments" }, - { "connection-oriented response fragments", "total connection-oriented response fragments" }, - { "connection-oriented client maximum fragment size", + { "Bind naks", "total connection-oriented bind naks" }, + { "Requests", "total connection-oriented requests" }, + { "Responses", "total connection-oriented responses" }, + { "Cancels", "total connection-oriented cancels" }, + { "Orphaned", "total connection-oriented orphaned" }, + { "Faults", "total connection-oriented faults" }, + { "Auth3s", "total connection-oriented auth3s" }, + { "Shutdowns", "total connection-oriented shutdowns" }, + { "Rejects", "total connection-oriented rejects" }, + { "MS RPC/HTTP PDUs", "total connection-oriented MS requests to send RPC over HTTP" }, + { "Other requests", "total connection-oriented other requests" }, + { "Other responses", "total connection-oriented other responses" }, + { "Request fragments", "total connection-oriented request fragments" }, + { "Response fragments", "total connection-oriented response fragments" }, + { "Client max fragment size", "connection-oriented client maximum fragment size" }, - { "connection-oriented client minimum fragment size", + { "Client min fragment size", "connection-oriented client minimum fragment size" }, - { "connection-oriented client segments reassembled", + { "Client segs reassembled", "total connection-oriented client segments reassembled" }, - { "connection-oriented client fragments reassembled", + { "Client frags reassembled", "total connection-oriented client fragments reassembled" }, - { "connection-oriented server maximum fragment size", + { "Server max fragment size", "connection-oriented server maximum fragment size" }, - { "connection-oriented server minimum fragment size", + { "Server min fragment size", "connection-oriented server minimum fragment size" }, - { "connection-oriented server segments reassembled", + { "Server segs reassembled", "total connection-oriented server segments reassembled" }, - { "connection-oriented server fragments reassembled", + { "Server frags reassembled", "total connection-oriented server fragments reassembled" }, + { "tcp sessions", "total tcp sessions" }, + { "tcp packets", "total tcp packets" }, { nullptr, nullptr } }; @@ -128,48 +129,48 @@ ProfileStats* Dce2TcpModule::get_profile( switch ( index ) { case 0: - name = "dce tcp main"; + name = "dce_tcp_main"; parent = nullptr; return &dce2_tcp_pstat_main; case 1: - name = "dce tcp session"; - parent = "dce tcp main"; + name = "dce_tcp_session"; + parent = "dce_tcp_main"; return &dce2_tcp_pstat_session; case 2: - name = "dce tcp new session"; - parent = "dce tcp session"; + name = "dce_tcp_new_session"; + parent = "dce_tcp_session"; return &dce2_tcp_pstat_new_session; case 3: - name = "dce tcp detect"; - parent = "dce tcp main"; + name = "dce_tcp_detect"; + parent = "dce_tcp_main"; return &dce2_tcp_pstat_detect; case 4: - name = "dce tcp log"; + name = "dce_tcp_log"; parent = "dce_tcp_main"; return &dce2_tcp_pstat_log; case 5: - name = "dce tcp connection-oriented segment"; - parent = "dce tcp main"; + name = "dce_tcp_co_segment"; + parent = "dce_tcp_main"; return &dce2_tcp_pstat_co_seg; case 6: - name = "dce tcp connection-oriented fragment"; - parent = "dce tcp main"; + name = "dce_tcp_co_fragment"; + parent = "dce_tcp_main"; return &dce2_tcp_pstat_co_frag; case 7: - name = "dce tcp connection-oriented reassembly"; - parent = "dce tcp main"; + name = "dce_tcp_co_reassembly"; + parent = "dce_tcp_main"; return &dce2_tcp_pstat_co_reass; case 8: - name = "dce tcp connection-oriented context"; - parent = "dce tcp main"; + name = "dce_tcp_co_context"; + parent = "dce_tcp_main"; return &dce2_tcp_pstat_co_ctx; } return nullptr; diff --git a/src/service_inspectors/dce_rpc/dce_tcp_paf.cc b/src/service_inspectors/dce_rpc/dce_tcp_paf.cc index a279a5292..fbde18f62 100644 --- a/src/service_inspectors/dce_rpc/dce_tcp_paf.cc +++ b/src/service_inspectors/dce_rpc/dce_tcp_paf.cc @@ -62,7 +62,7 @@ static StreamSplitter::Status dce2_tcp_paf(DCE2_PafTcpData* ds, Flow* flow, cons return StreamSplitter::ABORT; } - if (sd == NULL) + if (sd == nullptr) { bool autodetected = false; DebugMessage(DEBUG_DCE_TCP, "No session data - autodetecting\n"); @@ -102,7 +102,7 @@ static StreamSplitter::Status dce2_tcp_paf(DCE2_PafTcpData* ds, Flow* flow, cons while (n < len) { - DebugFormat(DEBUG_DCE_TCP, " State %d : 0x%02x", ds->paf_state, data[n]); + Debug::print(nullptr, 0, DEBUG_DCE_TCP, " State %d : 0x%02x\n", ds->paf_state, data[n]); switch (ds->paf_state) { @@ -111,15 +111,15 @@ static StreamSplitter::Status dce2_tcp_paf(DCE2_PafTcpData* ds, Flow* flow, cons ds->paf_state = (DCE2_PafTcpStates)(((int)ds->paf_state) + 1); if (ds->byte_order == DCERPC_BO_FLAG__LITTLE_ENDIAN) { - DebugMessage(DEBUG_DCE_TCP, "Byte order: Little endian\n"); + Debug::print(nullptr, 0, DEBUG_DCE_TCP, "%s","Byte order: Little endian\n"); } else { - DebugMessage(DEBUG_DCE_TCP, "Byte order: Big endian\n"); + Debug::print(nullptr, 0, DEBUG_DCE_TCP, "%s","Byte order: Big endian\n"); } break; case DCE2_PAF_TCP_STATES__8: - DebugMessage(DEBUG_DCE_TCP, "First byte of fragment length\n"); + Debug::print(nullptr, 0, DEBUG_DCE_TCP, "%s", "First byte of fragment length\n"); if (ds->byte_order == DCERPC_BO_FLAG__LITTLE_ENDIAN) ds->frag_len = data[n]; else @@ -127,7 +127,7 @@ static StreamSplitter::Status dce2_tcp_paf(DCE2_PafTcpData* ds, Flow* flow, cons ds->paf_state = (DCE2_PafTcpStates)(((int)ds->paf_state) + 1); break; case DCE2_PAF_TCP_STATES__9: - DebugMessage(DEBUG_DCE_TCP, "Second byte of fragment length\n"); + Debug::print(nullptr, 0, DEBUG_DCE_TCP, "%s", "Second byte of fragment length\n"); if (ds->byte_order == DCERPC_BO_FLAG__LITTLE_ENDIAN) ds->frag_len |= data[n] << 8; else @@ -136,11 +136,11 @@ static StreamSplitter::Status dce2_tcp_paf(DCE2_PafTcpData* ds, Flow* flow, cons /* If we get a bad frag length abort */ if (ds->frag_len < sizeof(DceRpcCoHdr)) { - DebugFormat(DEBUG_DCE_TCP, "%s\n", DCE2_DEBUG__PAF_END_MSG); + Debug::print(nullptr, 0, DEBUG_DCE_TCP, "%s\n", DCE2_DEBUG__PAF_END_MSG); return StreamSplitter::ABORT; } - DebugFormat(DEBUG_DCE_TCP, "Fragment length: %u\n", ds->frag_len); + Debug::print(nullptr, 0, DEBUG_DCE_TCP, "Fragment length: %u\n", ds->frag_len); /* Increment n here so we can continue */ n += ds->frag_len - (uint8_t)ds->paf_state; @@ -149,7 +149,7 @@ static StreamSplitter::Status dce2_tcp_paf(DCE2_PafTcpData* ds, Flow* flow, cons * flush just before it */ if ((num_requests == 1) || (n <= len)) tmp_fp += ds->frag_len; - DebugFormat(DEBUG_DCE_TCP, "Requests: %u\n", num_requests); + Debug::print(nullptr, 0, DEBUG_DCE_TCP, "Requests: %u\n", num_requests); ds->paf_state = DCE2_PAF_TCP_STATES__0; continue; // we incremented n already default: diff --git a/src/service_inspectors/dce_rpc/dce_utils.cc b/src/service_inspectors/dce_rpc/dce_utils.cc index dca58f59e..b5a5bb942 100644 --- a/src/service_inspectors/dce_rpc/dce_utils.cc +++ b/src/service_inspectors/dce_rpc/dce_utils.cc @@ -18,7 +18,7 @@ //-------------------------------------------------------------------------- #include "dce_utils.h" - +#include "main/snort_debug.h" /******************************************************************** * Function: DCE2_GetValue() @@ -59,7 +59,7 @@ DCE2_Ret DCE2_GetValue(char* start, char* end, void* int_value, int negate, uint64_t place = 1; uint64_t max_value = 0; - if ((end == NULL) || (start == NULL) || (int_value == NULL)) + if ((end == nullptr) || (start == nullptr) || (int_value == nullptr)) return DCE2_RET__ERROR; if (start >= end) @@ -160,3 +160,143 @@ DCE2_Ret DCE2_GetValue(char* start, char* end, void* int_value, int negate, return DCE2_RET__SUCCESS; } +const char* DCE2_UuidToStr(const Uuid* uuid, DceRpcBoFlag byte_order) +{ +#define UUID_BUF_SIZE 50 + static char uuid_buf1[UUID_BUF_SIZE]; + static char uuid_buf2[UUID_BUF_SIZE]; + static int buf_num = 0; + char* uuid_buf; + + if (buf_num == 0) + { + uuid_buf = uuid_buf1; + buf_num = 1; + } + else + { + uuid_buf = uuid_buf2; + buf_num = 0; + } + + snprintf(uuid_buf, UUID_BUF_SIZE, + "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + DceRpcHtonl(&uuid->time_low, byte_order), + DceRpcHtons(&uuid->time_mid, byte_order), + DceRpcHtons(&uuid->time_high_and_version, byte_order), + uuid->clock_seq_and_reserved, uuid->clock_seq_low, + uuid->node[0], uuid->node[1], uuid->node[2], + uuid->node[3], uuid->node[4], uuid->node[5]); + + uuid_buf[UUID_BUF_SIZE - 1] = '\0'; + + return uuid_buf; +} + +void DCE2_PrintPktData(const uint8_t* data, const uint16_t len) +{ + unsigned int i, j = 0, line_len = 0; + uint8_t hex_buf[16]; + uint8_t char_buf[16]; + + for (i = 0; i < len; i++) + { + hex_buf[j] = data[i]; + + if (isascii((int)data[i]) && isprint((int)data[i])) + char_buf[j] = data[i]; + else + char_buf[j] = '.'; + + if (line_len == 15) + { + unsigned int k, sub_line_len = 0; + for (k = 0; k <= j; k++) + { + Debug::print(nullptr, 0, DEBUG_DCE_COMMON,"%02x ", hex_buf[k]); + if (sub_line_len >= 7) + { + Debug::print(nullptr, 0, DEBUG_DCE_COMMON,"%s"," "); + sub_line_len = 0; + } + else + { + sub_line_len++; + } + } + + Debug::print(nullptr, 0, DEBUG_DCE_COMMON,"%s"," "); + + sub_line_len = 0; + for (k = 0; k <= j; k++) + { + Debug::print(nullptr, 0, DEBUG_DCE_COMMON,"%c", char_buf[k]); + if (sub_line_len >= 7) + { + Debug::print(nullptr, 0, DEBUG_DCE_COMMON,"%s"," "); + sub_line_len = 0; + } + else + { + sub_line_len++; + } + } + + Debug::print(nullptr, 0, DEBUG_DCE_COMMON,"%s","\n"); + + j = line_len = 0; + } + else + { + j++; + line_len++; + } + } + + if (line_len > 0) + { + unsigned int k, sub_line_len = 0; + for (k = 0; k < j; k++) + { + Debug::print(nullptr, 0, DEBUG_DCE_COMMON,"%02x ", hex_buf[k]); + if (sub_line_len >= 7) + { + Debug::print(nullptr, 0, DEBUG_DCE_COMMON,"%s"," "); + sub_line_len = 0; + } + else + { + sub_line_len++; + } + } + + if (k < 8) + Debug::print(nullptr, 0, DEBUG_DCE_COMMON,"%s"," "); + else + Debug::print(nullptr, 0, DEBUG_DCE_COMMON,"%s"," "); + + while (k < 16) + { + Debug::print(nullptr, 0, DEBUG_DCE_COMMON,"%s"," "); + k++; + } + + sub_line_len = 0; + for (k = 0; k < j; k++) + { + Debug::print(nullptr, 0, DEBUG_DCE_COMMON,"%c", char_buf[k]); + if (sub_line_len >= 7) + { + Debug::print(nullptr, 0, DEBUG_DCE_COMMON,"%s"," "); + sub_line_len = 0; + } + else + { + sub_line_len++; + } + } + } + + Debug::print(nullptr, 0, DEBUG_DCE_COMMON,"%s","\n"); +} + diff --git a/src/service_inspectors/dce_rpc/dce_utils.h b/src/service_inspectors/dce_rpc/dce_utils.h index e923f1ed4..745a69cc2 100644 --- a/src/service_inspectors/dce_rpc/dce_utils.h +++ b/src/service_inspectors/dce_rpc/dce_utils.h @@ -70,6 +70,14 @@ enum DCE2_IntType DCE2_INT_TYPE__UINT64 }; +/* DCE/RPC byte order flag */ +enum DceRpcBoFlag +{ + DCERPC_BO_FLAG__NONE, + DCERPC_BO_FLAG__BIG_ENDIAN, + DCERPC_BO_FLAG__LITTLE_ENDIAN +}; + /******************************************************************** * Structures ********************************************************************/ @@ -104,6 +112,8 @@ inline char* DCE2_PruneWhiteSpace(char*); inline bool DCE2_IsEmptyStr(char*); inline int DCE2_UuidCompare(const void*, const void*); +const char* DCE2_UuidToStr(const Uuid*, DceRpcBoFlag); +void DCE2_PrintPktData(const uint8_t*, const uint16_t); /******************************************************************** * Public function prototypes @@ -253,5 +263,120 @@ inline int DCE2_UuidCompare(const void* data1, const void* data2) return -1; } +inline DceRpcBoFlag DceRpcByteOrder(const uint8_t value) +{ + if ((value & 0x10) >> 4) + return DCERPC_BO_FLAG__LITTLE_ENDIAN; + + return DCERPC_BO_FLAG__BIG_ENDIAN; +} + +inline uint16_t DceRpcNtohs(const uint16_t* ptr, const DceRpcBoFlag bo_flag) +{ + uint16_t value; + + if (ptr == nullptr) + return 0; + +#ifdef WORDS_MUSTALIGN + value = *((uint8_t*)ptr) << 8 | *((uint8_t*)ptr + 1); +#else + value = *ptr; +#endif /* WORDS_MUSTALIGN */ + + if (bo_flag == DCERPC_BO_FLAG__NONE) + return value; + +#ifdef WORDS_BIGENDIAN + if (bo_flag == DCERPC_BO_FLAG__BIG_ENDIAN) +#else + if (bo_flag == DCERPC_BO_FLAG__LITTLE_ENDIAN) +#endif /* WORDS_BIGENDIAN */ + return value; + + return ((value & 0xff00) >> 8) | ((value & 0x00ff) << 8); +} + +inline uint16_t DceRpcHtons(const uint16_t* ptr, const DceRpcBoFlag bo_flag) +{ + return DceRpcNtohs(ptr, bo_flag); +} + +inline uint32_t DceRpcNtohl(const uint32_t* ptr, const DceRpcBoFlag bo_flag) +{ + uint32_t value; + + if (ptr == nullptr) + return 0; + +#ifdef WORDS_MUSTALIGN + value = *((uint8_t*)ptr) << 24 | *((uint8_t*)ptr + 1) << 16 | + *((uint8_t*)ptr + 2) << 8 | *((uint8_t*)ptr + 3); +#else + value = *ptr; +#endif /* WORDS_MUSTALIGN */ + + if (bo_flag == DCERPC_BO_FLAG__NONE) + return value; + +#ifdef WORDS_BIGENDIAN + if (bo_flag == DCERPC_BO_FLAG__BIG_ENDIAN) +#else + if (bo_flag == DCERPC_BO_FLAG__LITTLE_ENDIAN) +#endif /* WORDS_BIGENDIAN */ + return value; + + return ((value & 0xff000000) >> 24) | ((value & 0x00ff0000) >> 8) | + ((value & 0x0000ff00) << 8) | ((value & 0x000000ff) << 24); +} + +inline uint32_t DceRpcHtonl(const uint32_t* ptr, const DceRpcBoFlag bo_flag) +{ + return DceRpcNtohl(ptr, bo_flag); +} + +inline void DCE2_CopyUuid(Uuid* dst_uuid, const Uuid* pkt_uuid, const DceRpcBoFlag byte_order) +{ + dst_uuid->time_low = DceRpcNtohl(&pkt_uuid->time_low, byte_order); + dst_uuid->time_mid = DceRpcNtohs(&pkt_uuid->time_mid, byte_order); + dst_uuid->time_high_and_version = DceRpcNtohs(&pkt_uuid->time_high_and_version, byte_order); + dst_uuid->clock_seq_and_reserved = pkt_uuid->clock_seq_and_reserved; + dst_uuid->clock_seq_low = pkt_uuid->clock_seq_low; + memcpy(dst_uuid->node, pkt_uuid->node, sizeof(dst_uuid->node)); +} + +inline int DCE2_BufferIsEmpty(DCE2_Buffer* buf) +{ + if (buf == nullptr) + return 1; + if ((buf->data == nullptr) || (buf->len == 0)) + return 1; + return 0; +} + +inline uint32_t DCE2_BufferLength(DCE2_Buffer* buf) +{ + if (buf == nullptr) + return 0; + return buf->len; +} + +inline uint8_t* DCE2_BufferData(DCE2_Buffer* buf) +{ + if (buf == nullptr) + return nullptr; + return buf->data; +} + +inline void DCE2_BufferEmpty(DCE2_Buffer* buf) +{ + if (buf == nullptr) + return; + buf->len = 0; +} + +#define DCE2_MOVE(data_ptr, data_len, amount) \ + { data_len -= (amount); data_ptr = (uint8_t*)data_ptr + (amount); } + #endif /* _DCE2_UTILS_H_ */