From e0f20d21216763ae5d97407ed151e101e18c9e2b Mon Sep 17 00:00:00 2001 From: seladb Date: Sat, 1 Jun 2024 22:16:31 -0700 Subject: [PATCH] Add LDAP layer (#1412) * Add LDAP layer * Add doxygen * Parse multiple LDAP messages in one packet * Add tryGet * Fix pre-commit * Fix pre-commit * Temporarily replace Npcap with WinPcap * Fix WinPcap path * Modify `EnumClassHash` * Modify Int -> Uint * Revert CI changes * Add field index constexpr * Address PR comment: use `std::mem_fn` * Address PR comments --- Common++/header/GeneralUtils.h | 14 + Packet++/CMakeLists.txt | 2 + Packet++/header/Asn1Codec.h | 11 + Packet++/header/LdapLayer.h | 275 ++++++++++++++++++ Packet++/header/ProtocolType.h | 5 + Packet++/src/Asn1Codec.cpp | 36 +-- Packet++/src/LdapLayer.cpp | 211 ++++++++++++++ Packet++/src/TcpLayer.cpp | 7 + Tests/Packet++Test/CMakeLists.txt | 1 + Tests/Packet++Test/PacketExamples/ldap.pcapng | Bin 0 -> 7172 bytes .../PacketExamples/ldap_add_response.dat | 1 + .../PacketExamples/ldap_bind_request1.dat | 1 + .../PacketExamples/ldap_multiple_messages.dat | 1 + .../PacketExamples/ldap_search_request1.dat | 1 + Tests/Packet++Test/TestDefinition.h | 4 + Tests/Packet++Test/Tests/Asn1Tests.cpp | 22 +- Tests/Packet++Test/Tests/LdapTests.cpp | 169 +++++++++++ Tests/Packet++Test/main.cpp | 3 + Tests/PcppTestFramework/PcppTestFramework.h | 24 ++ 19 files changed, 770 insertions(+), 18 deletions(-) create mode 100644 Packet++/header/LdapLayer.h create mode 100644 Packet++/src/LdapLayer.cpp create mode 100644 Tests/Packet++Test/PacketExamples/ldap.pcapng create mode 100644 Tests/Packet++Test/PacketExamples/ldap_add_response.dat create mode 100644 Tests/Packet++Test/PacketExamples/ldap_bind_request1.dat create mode 100644 Tests/Packet++Test/PacketExamples/ldap_multiple_messages.dat create mode 100644 Tests/Packet++Test/PacketExamples/ldap_search_request1.dat create mode 100644 Tests/Packet++Test/Tests/LdapTests.cpp diff --git a/Common++/header/GeneralUtils.h b/Common++/header/GeneralUtils.h index 198139008a..1f7e475b05 100644 --- a/Common++/header/GeneralUtils.h +++ b/Common++/header/GeneralUtils.h @@ -2,6 +2,7 @@ #include #include +#include /// @file @@ -64,4 +65,17 @@ namespace pcpp int mask = alignment - 1; return (number + mask) & ~mask; } + + /** + * A template class to calculate enum class hash + * @tparam EnumClass + */ + template::value , bool>::type = false> + struct EnumClassHash + { + size_t operator()(EnumClass value) const + { + return static_cast::type>(value); + } + }; } diff --git a/Packet++/CMakeLists.txt b/Packet++/CMakeLists.txt index a0c4143929..cc7a8268d2 100644 --- a/Packet++/CMakeLists.txt +++ b/Packet++/CMakeLists.txt @@ -25,6 +25,7 @@ add_library( src/IPv6Extensions.cpp src/IPv6Layer.cpp src/Layer.cpp + src/LdapLayer.cpp src/LLCLayer.cpp src/MplsLayer.cpp src/NdpLayer.cpp @@ -93,6 +94,7 @@ set(public_headers header/IPv6Extensions.h header/IPv6Layer.h header/Layer.h + header/LdapLayer.h header/LLCLayer.h header/MplsLayer.h header/NullLoopbackLayer.h diff --git a/Packet++/header/Asn1Codec.h b/Packet++/header/Asn1Codec.h index 1bd41c1d13..7eeb48a526 100644 --- a/Packet++/header/Asn1Codec.h +++ b/Packet++/header/Asn1Codec.h @@ -242,6 +242,15 @@ namespace pcpp */ Asn1GenericRecord(Asn1TagClass tagClass, bool isConstructed, uint8_t tagType, const uint8_t* value, size_t valueLen); + /** + * A constructor to create a generic record + * @param tagClass The record tag class + * @param isConstructed A flag to indicate if the record is constructed or primitive + * @param tagType The record tag type value + * @param value A string representing the tag value + */ + Asn1GenericRecord(Asn1TagClass tagClass, bool isConstructed, uint8_t tagType, const std::string& value); + ~Asn1GenericRecord() override; /** @@ -257,6 +266,8 @@ namespace pcpp private: uint8_t* m_Value = nullptr; + + void init(Asn1TagClass tagClass, bool isConstructed, uint8_t tagType, const uint8_t* value, size_t valueLen); }; /** diff --git a/Packet++/header/LdapLayer.h b/Packet++/header/LdapLayer.h new file mode 100644 index 0000000000..188403fb73 --- /dev/null +++ b/Packet++/header/LdapLayer.h @@ -0,0 +1,275 @@ +#pragma once + +#include "Layer.h" +#include "Asn1Codec.h" +#include +#include +#include + +/// @file + +/** + * @namespace pcpp + * @brief The main namespace for the PcapPlusPlus lib + */ +namespace pcpp +{ + /** + * @class LdapOperationType + * @brief An enum wrapper class for LDAP operation types + */ + class LdapOperationType + { + public: + /** + * Define enum types and the corresponding int values + */ + enum Value : uint8_t + { + /// Bind Request + BindRequest = 0, + /// Bind Response + BindResponse = 1, + /// Unbind Request + UnbindRequest = 2, + /// Search Request + SearchRequest = 3, + /// Search Result Entry + SearchResultEntry = 4, + /// Search Result Done + SearchResultDone = 5, + /// Modify Request + ModifyRequest = 6, + /// Modify Response + ModifyResponse = 7, + /// Add Request + AddRequest = 8, + /// Add Response + AddResponse = 9, + /// Delete Request + DelRequest = 10, + /// Delete Response + DelResponse = 11, + /// Modify DN (Distinguished Name) Request + ModifyDNRequest = 12, + /// Modify DN (Distinguished Name) Response + ModifyDNResponse = 13, + /// Compare Request + CompareRequest = 14, + /// Compare Response + CompareResponse = 15, + /// Abandon Request + AbandonRequest = 16, + /// Search Result Reference + SearchResultReference = 19, + /// Extended Request + ExtendedRequest = 23, + /// Extended Response + ExtendedResponse = 24, + /// Intermediate Response + IntermediateResponse = 25, + /// Unknown operation type + Unknown = 255 + }; + + LdapOperationType() = default; + + // cppcheck-suppress noExplicitConstructor + /** + * Construct LdapOperationType from Value enum + * @param[in] value the opetation type enum value + */ + constexpr LdapOperationType(Value value) : m_Value(value) {} + + /** + * @return A string representation of the operation type + */ + std::string toString() const; + + /** + * A static method that creates LdapOperationType from an integer value + * @param[in] value The operation type integer value + * @return The operation type that corresponds to the integer value. If the integer value + * doesn't corresponds to any operation type, LdapOperationType::Unknown is returned + */ + static LdapOperationType fromUintValue(uint8_t value); + + // Allow switch and comparisons. + constexpr operator Value() const { return m_Value; } + + // Prevent usage: if(LdapOperationType) + explicit operator bool() const = delete; + + private: + Value m_Value = LdapOperationType::Unknown; + }; + + /** + * @struct LdapControl + * A struct that represents an LDAP Control + */ + struct LdapControl + { + /// LDAP control type + std::string controlType; + /// LDAP control value + std::string controlValue; + + /** + * Equality operator overload for this struct + * @param[in] other The value to compare with + * @return True if both values are equal, false otherwise + */ + bool operator==(const LdapControl& other) const + { + return controlType == other.controlType && controlValue == other.controlValue; + } + }; + + /** + * @class LdapLayer + * Represents an LDAP message + */ + class LdapLayer : public Layer + { + public: + /** + * A constructor to create a new LDAP message + * @param[in] messageId The LDAP message ID + * @param[in] operationType The LDAP operation type + * @param[in] messageRecords A vector of ASN.1 records that comprise the LDAP message + * @param[in] controls A vector of LDAP controls. This is an optional parameter, if not provided the message + * will be created without LDAP controls + */ + LdapLayer(uint16_t messageId, LdapOperationType operationType, + const std::vector& messageRecords, + const std::vector& controls = std::vector()); + + ~LdapLayer() {} + + /** + * @return The root ASN.1 record of the LDAP message. All of the message data will be under this record. + * If the Root ASN.1 record is malformed, an exception is thrown + */ + Asn1SequenceRecord* getRootAsn1Record() const; + + /** + * @return The ASN.1 record of the specific LDAP operation in this LDAP message. Each operation has a specific + * structure. If the Operation ASN.1 record is malformed, an exception is thrown + */ + Asn1ConstructedRecord* getLdapOperationAsn1Record() const; + + /** + * @return The LDAP message ID. If the ASN.1 record is malformed, an exception is thrown + */ + uint16_t getMessageID() const; + + /** + * @return A vector of LDAP controls in this message. If the message contains no controls then an empty + * vector is returned. If the Controls ASN.1 record is malformed, an exception is thrown + */ + std::vector getControls() const; + + /** + * @return The LDAP operation of this message. If the Operation ASN.1 record is malformed, an exception is thrown + */ + LdapOperationType getLdapOperationType() const; + + /** + * Most getter methods in this class throw an exception if the corresponding ASN.1 record is invalid. + * This is a wrapper method that allows calling these getters without adding a `try...catch` clause. + * It accepts the getter method and an out variable. It tries to call the getter and if no exception + * is thrown, the out variable will contain the result. + * + * Here is an example: + * @code + * uint16_t messageId; + * ldapLayer->tryGet(&pcpp::LdapLayer::getMessageID, messageId)); + * @endcode + * + * We call getMessageID(), if no exception is thrown the variable messageId will hold the result + * + * @tparam Method The class method type + * @tparam ResultType The expected result type (for example: uint8_t, std::string, etc.) + * @param[in] method The class method to call + * @param[out] result An outvariable to contain the result if no exception is thrown + * @return True if no exception was thrown or false otherwise + */ + template + bool tryGet(Method method, ResultType& result) + { + return internalTryGet(this, method, result); + } + + /** + * A static method that checks whether a source or dest port match those associated with the LDAP protocol + * @param[in] port The port number to check + * @return True if this is an LDAP port, false otherwise + */ + static bool isLdapPort(uint16_t port) { return port == 389; } + + /** + * A static message to parse an LDAP message from raw data + * @param[in] data A pointer to the raw data + * @param[in] dataLen Size of the data in bytes + * @param[in] prevLayer A pointer to the previous layer + * @param[in] packet A pointer to the Packet instance where layer will be stored in + * @return An instance of LdapLayer if this is indeed an LDAP message, nullptr otherwise + */ + static LdapLayer* parseLdapMessage(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet); + + // implement abstract methods + + /** + * Tries to identify more LDAP messages in this packet if exist + */ + void parseNextLayer() override; + + /** + * @return The size of the LDAP message + */ + size_t getHeaderLen() const override { return m_Asn1Record->getTotalLength(); } + + void computeCalculateFields() override {} + + OsiModelLayer getOsiModelLayer() const override { return OsiModelApplicationLayer; } + + std::string toString() const override; + + protected: + std::unique_ptr m_Asn1Record; + + LdapLayer(std::unique_ptr& asn1Record, uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet); + LdapLayer() = default; + void init(uint16_t messageId, LdapOperationType operationType, const std::vector& messageRecords, const std::vector& controls); + virtual std::string getExtendedStringInfo() const { return ""; } + + static constexpr int messageIdIndex = 0; + static constexpr int operationTypeIndex = 1; + static constexpr int controlsIndex = 2; + + static constexpr int controlTypeIndex = 0; + static constexpr int controlValueIndex = 1; + + template + bool internalTryGet(LdapClass* thisPtr, Method method, ResultType& result) + { + try + { + result = std::mem_fn(method)(thisPtr); + return true; + } + catch (...) + { + return false; + } + } + }; + +} // namespace pcpp + +inline std::ostream& operator<<(std::ostream& os, const pcpp::LdapControl& control) +{ + os << "{" << control.controlType << ", " << control.controlValue << "}"; + return os; +} diff --git a/Packet++/header/ProtocolType.h b/Packet++/header/ProtocolType.h index 85a85a7f4a..0f7cba2bbd 100644 --- a/Packet++/header/ProtocolType.h +++ b/Packet++/header/ProtocolType.h @@ -342,6 +342,11 @@ namespace pcpp */ const ProtocolType SMTP = 54; + /* + * LDAP protocol + */ + const ProtocolType LDAP = 55; + /** * An enum representing OSI model layers */ diff --git a/Packet++/src/Asn1Codec.cpp b/Packet++/src/Asn1Codec.cpp index 846d740315..98855989b0 100644 --- a/Packet++/src/Asn1Codec.cpp +++ b/Packet++/src/Asn1Codec.cpp @@ -3,6 +3,7 @@ #include "Asn1Codec.h" #include "GeneralUtils.h" #include "EndianPortable.h" +#include "GeneralUtils.h" #include #include #include @@ -18,15 +19,6 @@ namespace pcpp { - template - struct EnumClassHash - { - size_t operator()(const EnumClass& value) const - { - return static_cast(value); - } - }; - const std::unordered_map> Asn1TagClassToString { {Asn1TagClass::Universal, "Universal" }, {Asn1TagClass::ContextSpecific, "ContextSpecific" }, @@ -443,13 +435,12 @@ namespace pcpp Asn1GenericRecord::Asn1GenericRecord(Asn1TagClass tagClass, bool isConstructed, uint8_t tagType, const uint8_t* value, size_t valueLen) { - m_TagType = tagType; - m_TagClass = tagClass; - m_IsConstructed = isConstructed; - m_Value = new uint8_t[valueLen]; - memcpy(m_Value, value, valueLen); - m_ValueLength = valueLen; - m_TotalLength = m_ValueLength + 2; + init(tagClass, isConstructed, tagType, value, valueLen); + } + + Asn1GenericRecord::Asn1GenericRecord(Asn1TagClass tagClass, bool isConstructed, uint8_t tagType, const std::string& value) + { + init(tagClass, isConstructed, tagType, reinterpret_cast(value.c_str()), value.size()); } Asn1GenericRecord::~Asn1GenericRecord() @@ -470,6 +461,17 @@ namespace pcpp return {m_Value, m_Value + m_ValueLength}; } + void Asn1GenericRecord::init(Asn1TagClass tagClass, bool isConstructed, uint8_t tagType, const uint8_t* value, size_t valueLen) + { + m_TagType = tagType; + m_TagClass = tagClass; + m_IsConstructed = isConstructed; + m_Value = new uint8_t[valueLen]; + memcpy(m_Value, value, valueLen); + m_ValueLength = valueLen; + m_TotalLength = m_ValueLength + 2; + } + Asn1ConstructedRecord::Asn1ConstructedRecord(Asn1TagClass tagClass, uint8_t tagType, const std::vector& subRecords) { init(tagClass, tagType, subRecords.begin(), subRecords.end()); @@ -683,7 +685,7 @@ namespace pcpp auto value = reinterpret_cast(data); m_IsPrintable = std::all_of(value, value + m_ValueLength, [](char c) { - return isprint(c); + return isprint(0xff & c); }); if (m_IsPrintable) diff --git a/Packet++/src/LdapLayer.cpp b/Packet++/src/LdapLayer.cpp new file mode 100644 index 0000000000..1a4cb06e81 --- /dev/null +++ b/Packet++/src/LdapLayer.cpp @@ -0,0 +1,211 @@ +#include "LdapLayer.h" +#include "GeneralUtils.h" +#include + +namespace pcpp { + + // region LdapOperationType + + const std::unordered_map> LdapOperationTypeToString{ + {LdapOperationType::BindRequest, "BindRequest"}, + {LdapOperationType::BindResponse, "BindResponse"}, + {LdapOperationType::UnbindRequest, "UnbindRequest"}, + {LdapOperationType::SearchRequest, "SearchRequest"}, + {LdapOperationType::SearchResultEntry, "SearchResultEntry"}, + {LdapOperationType::SearchResultDone, "SearchResultDone"}, + {LdapOperationType::ModifyRequest, "ModifyRequest"}, + {LdapOperationType::ModifyResponse, "ModifyResponse"}, + {LdapOperationType::AddRequest, "AddRequest"}, + {LdapOperationType::AddResponse, "AddResponse"}, + {LdapOperationType::DelRequest, "DelRequest"}, + {LdapOperationType::DelResponse, "DelResponse"}, + {LdapOperationType::ModifyDNRequest, "ModifyDNRequest"}, + {LdapOperationType::ModifyDNResponse, "ModifyDNResponse"}, + {LdapOperationType::CompareRequest, "CompareRequest"}, + {LdapOperationType::CompareResponse, "CompareResponse"}, + {LdapOperationType::AbandonRequest, "AbandonRequest"}, + {LdapOperationType::SearchResultReference, "SearchResultReference"}, + {LdapOperationType::ExtendedRequest, "ExtendedRequest"}, + {LdapOperationType::ExtendedResponse, "ExtendedResponse"}, + {LdapOperationType::IntermediateResponse, "IntermediateResponse"}, + {LdapOperationType::Unknown, "Unknown"} + }; + + const std::unordered_map UintToLdapOperationType{ + {static_cast(LdapOperationType::BindRequest), LdapOperationType::BindRequest}, + {static_cast(LdapOperationType::BindResponse), LdapOperationType::BindResponse}, + {static_cast(LdapOperationType::UnbindRequest), LdapOperationType::UnbindRequest}, + {static_cast(LdapOperationType::SearchRequest), LdapOperationType::SearchRequest}, + {static_cast(LdapOperationType::SearchResultEntry), LdapOperationType::SearchResultEntry}, + {static_cast(LdapOperationType::SearchResultDone), LdapOperationType::SearchResultDone}, + {static_cast(LdapOperationType::ModifyResponse), LdapOperationType::ModifyResponse}, + {static_cast(LdapOperationType::AddRequest), LdapOperationType::AddRequest}, + {static_cast(LdapOperationType::AddResponse), LdapOperationType::AddResponse}, + {static_cast(LdapOperationType::DelRequest), LdapOperationType::DelRequest}, + {static_cast(LdapOperationType::DelResponse), LdapOperationType::DelResponse}, + {static_cast(LdapOperationType::ModifyDNRequest), LdapOperationType::ModifyDNRequest}, + {static_cast(LdapOperationType::ModifyDNResponse), LdapOperationType::ModifyDNResponse}, + {static_cast(LdapOperationType::CompareRequest), LdapOperationType::CompareRequest}, + {static_cast(LdapOperationType::CompareResponse), LdapOperationType::CompareResponse}, + {static_cast(LdapOperationType::AbandonRequest), LdapOperationType::AbandonRequest}, + {static_cast(LdapOperationType::SearchResultReference), LdapOperationType::SearchResultReference}, + {static_cast(LdapOperationType::ExtendedRequest), LdapOperationType::ExtendedRequest}, + {static_cast(LdapOperationType::ExtendedResponse), LdapOperationType::ExtendedResponse}, + {static_cast(LdapOperationType::IntermediateResponse), LdapOperationType::IntermediateResponse} + }; + + std::string LdapOperationType::toString() const + { + return LdapOperationTypeToString.at(m_Value); + } + + LdapOperationType LdapOperationType::fromUintValue(uint8_t value) + { + auto result = UintToLdapOperationType.find(value); + if (result != UintToLdapOperationType.end()) + { + return result->second; + } + + return LdapOperationType::Unknown; + } + + // endregion + + // region LdapLayer + + LdapLayer::LdapLayer(uint16_t messageId, LdapOperationType operationType, + const std::vector& messageRecords, const std::vector& controls) + { + init(messageId, operationType, messageRecords, controls); + } + + LdapLayer::LdapLayer(std::unique_ptr& asn1Record, uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet) : Layer(data, dataLen, prevLayer, packet) + { + m_Protocol = LDAP; + m_Asn1Record = std::move(asn1Record); + } + + void LdapLayer::init(uint16_t messageId, LdapOperationType operationType, const std::vector& messageRecords, const std::vector& controls) + { + Asn1IntegerRecord messageIdRecord(messageId); + Asn1ConstructedRecord messageRootRecord(Asn1TagClass::Application, operationType, messageRecords); + + std::vector rootSubRecords = {&messageIdRecord, &messageRootRecord}; + + std::unique_ptr controlsRecord; + if (!controls.empty()) + { + PointerVector controlsSubRecords; + for (const auto& control : controls) + { + Asn1OctetStringRecord controlTypeRecord(control.controlType); + if (control.controlValue.empty()) + { + controlsSubRecords.pushBack(new Asn1SequenceRecord({&controlTypeRecord})); + } + else + { + auto controlValueSize = static_cast(control.controlValue.size() / 2); + std::unique_ptr controlValue(new uint8_t[controlValueSize]); + controlValueSize = hexStringToByteArray(control.controlValue, controlValue.get(), controlValueSize); + Asn1OctetStringRecord controlValueRecord(controlValue.get(), controlValueSize); + controlsSubRecords.pushBack(new Asn1SequenceRecord({&controlTypeRecord, &controlValueRecord})); + } + } + controlsRecord = std::unique_ptr(new Asn1ConstructedRecord(Asn1TagClass::ContextSpecific, 0, controlsSubRecords)); + rootSubRecords.push_back(controlsRecord.get()); + } + + Asn1SequenceRecord rootRecord(rootSubRecords); + + auto encodedData = rootRecord.encode(); + m_DataLen = encodedData.size(); + m_Data = new uint8_t[m_DataLen]; + std::copy(encodedData.begin(), encodedData.end(), m_Data); + m_Protocol = LDAP; + m_Asn1Record = Asn1Record::decode(m_Data, m_DataLen, true); + } + + std::string LdapLayer::toString() const + { + auto extendedInfo = getExtendedStringInfo(); + return "LDAP Layer, " + getLdapOperationType().toString() + (extendedInfo.empty() ? "" : ", " + extendedInfo); + } + + LdapLayer* LdapLayer::parseLdapMessage(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet) + { + try + { + auto asn1Record = Asn1Record::decode(data, dataLen, true); + auto operationType = LdapOperationType::fromUintValue(asn1Record->castAs()->getSubRecords().at(operationTypeIndex)->getTagType()); + if (operationType != LdapOperationType::Unknown) + { + return new LdapLayer(asn1Record, data, dataLen, prevLayer, packet); + } + + return nullptr; + } + catch (...) + { + return nullptr; + } + } + + Asn1SequenceRecord* LdapLayer::getRootAsn1Record() const + { + return m_Asn1Record->castAs(); + } + + Asn1ConstructedRecord* LdapLayer::getLdapOperationAsn1Record() const + { + return getRootAsn1Record()->getSubRecords().at(operationTypeIndex)->castAs(); + } + + uint16_t LdapLayer::getMessageID() const + { + return getRootAsn1Record()->getSubRecords().at(messageIdIndex)->castAs()->getValue(); + } + + std::vector LdapLayer::getControls() const + { + std::vector controls; + if (getRootAsn1Record()->getSubRecords().size() <= controlsIndex) + { + return controls; + } + + auto controlsRecord = getRootAsn1Record()->getSubRecords().at(controlsIndex)->castAs(); + for (auto controlRecord : controlsRecord->getSubRecords()) + { + auto controlSequence = controlRecord->castAs(); + auto controlType = controlSequence->getSubRecords().at(controlTypeIndex)->castAs()->getValue(); + std::string controlValue; + if (controlSequence->getSubRecords().size() > controlValueIndex) + { + controlValue = controlSequence->getSubRecords().at(controlValueIndex)->castAs()->getValue(); + } + controls.push_back({ controlType, controlValue }); + } + + return controls; + } + + LdapOperationType LdapLayer::getLdapOperationType() const + { + return LdapOperationType::fromUintValue(getLdapOperationAsn1Record()->getTagType()); + } + + void LdapLayer::parseNextLayer() + { + size_t headerLen = getHeaderLen(); + if (m_DataLen <= headerLen || headerLen == 0) + return; + + uint8_t* payload = m_Data + headerLen; + size_t payloadLen = m_DataLen - headerLen; + + m_NextLayer = LdapLayer::parseLdapMessage(payload, payloadLen, this, m_Packet); + } + // endregion +} diff --git a/Packet++/src/TcpLayer.cpp b/Packet++/src/TcpLayer.cpp index 85bc08d871..e992cf213a 100644 --- a/Packet++/src/TcpLayer.cpp +++ b/Packet++/src/TcpLayer.cpp @@ -16,6 +16,7 @@ #include "FtpLayer.h" #include "SomeIpLayer.h" #include "SmtpLayer.h" +#include "LdapLayer.h" #include "PacketUtils.h" #include "Logger.h" #include "DeprecationUtils.h" @@ -404,6 +405,12 @@ void TcpLayer::parseNextLayer() m_NextLayer = new SmtpResponseLayer(payload, payloadLen, this, m_Packet); else if (SmtpLayer::isSmtpPort(portDst) && SmtpLayer::isDataValid(payload, payloadLen)) m_NextLayer = new SmtpRequestLayer(payload, payloadLen, this, m_Packet); + else if (LdapLayer::isLdapPort(portDst) || LdapLayer::isLdapPort(portSrc)) + { + m_NextLayer = LdapLayer::parseLdapMessage(payload, payloadLen, this, m_Packet); + if (!m_NextLayer) + m_NextLayer = new PayloadLayer(payload, payloadLen, this, m_Packet); + } else m_NextLayer = new PayloadLayer(payload, payloadLen, this, m_Packet); } diff --git a/Tests/Packet++Test/CMakeLists.txt b/Tests/Packet++Test/CMakeLists.txt index 8776d170a6..0e6bbc75bc 100644 --- a/Tests/Packet++Test/CMakeLists.txt +++ b/Tests/Packet++Test/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable( Tests/IPSecTests.cpp Tests/IPv4Tests.cpp Tests/IPv6Tests.cpp + Tests/LdapTests.cpp Tests/LLCTests.cpp Tests/NflogTests.cpp Tests/NtpTests.cpp diff --git a/Tests/Packet++Test/PacketExamples/ldap.pcapng b/Tests/Packet++Test/PacketExamples/ldap.pcapng new file mode 100644 index 0000000000000000000000000000000000000000..efa0e15773a375bb857bf0d818e4a52286420f43 GIT binary patch literal 7172 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 literal 0 HcmV?d00001 diff --git a/Tests/Packet++Test/PacketExamples/ldap_add_response.dat b/Tests/Packet++Test/PacketExamples/ldap_add_response.dat new file mode 100644 index 0000000000..97562f1d2c --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/ldap_add_response.dat @@ -0,0 +1 @@ +0800272cf6a208b4b11a46ad0800450000420e76400023065b9e3439a258c0a85668018589f1473d0c997a93c5b5801801e3190300000101080af7118110ec708c75300c02011b69070a010004000400 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/ldap_bind_request1.dat b/Tests/Packet++Test/PacketExamples/ldap_bind_request1.dat new file mode 100644 index 0000000000..8c046a8464 --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/ldap_bind_request1.dat @@ -0,0 +1 @@ +000c2932503f000c29a01b430800450000aaea6840004006c98cc0a80285c0a8028386110185d91123158f18ef878018013fe79900000101080a00dbc875008b457130740201026050020103043b636e3d41646d696e6973747261746f722c636e3d55736572732c64633d636c6f7564736861726b2d612c64633d6578616d706c652c64633d636f6d800e636c6f7564736861726b31323321a01d301b0419312e332e362e312e342e312e34322e322e32372e382e352e31 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/ldap_multiple_messages.dat b/Tests/Packet++Test/PacketExamples/ldap_multiple_messages.dat new file mode 100644 index 0000000000..85c614fc49 --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/ldap_multiple_messages.dat @@ -0,0 +1 @@ +000000010006000c298dc78100000800450001673819400080062ad5c0a80abac0a80a98018594958b4216846eeb30b050182010b0e8000030840000005202010673840000004904476c6461703a2f2f466f72657374446e735a6f6e65732e6d61747269782e6c6f63616c2f44433d466f72657374446e735a6f6e65732c44433d6d61747269782c44433d6c6f63616c30840000005202010673840000004904476c6461703a2f2f446f6d61696e446e735a6f6e65732e6d61747269782e6c6f63616c2f44433d446f6d61696e446e735a6f6e65732c44433d6d61747269782c44433d6c6f63616c30840000004202010673840000003904376c6461703a2f2f6d61747269782e6c6f63616c2f434e3d436f6e66696775726174696f6e2c44433d6d61747269782c44433d6c6f63616c3084000000410201066584000000070a010004000400a0840000002b3084000000250416312e322e3834302e3131333535362e312e342e333139040b3084000000050201000400 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/ldap_search_request1.dat b/Tests/Packet++Test/PacketExamples/ldap_search_request1.dat new file mode 100644 index 0000000000..0b05660f18 --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/ldap_search_request1.dat @@ -0,0 +1 @@ +000400010006000c29c3a87900000800450000f01fea40004006837bc0a80a98c0a80aba949501856eeb2fe88b42168450180f4a978500003081c50201066379041244433d6d61747269782c44433d6c6f63616c0a01020a0103020100020100010100a939811c322e31362e3834302e312e3131333733302e332e332e322e34362e3182106465706172746d656e744e756d62657283073e3d4e34373039301904012a04146e74736563757269747964657363726970746f72a045301f0416312e322e3834302e3131333535362e312e342e3830310405300302010730220416312e322e3834302e3131333535362e312e342e33313904083006020201f40400 \ No newline at end of file diff --git a/Tests/Packet++Test/TestDefinition.h b/Tests/Packet++Test/TestDefinition.h index baa566accd..b628c34f56 100644 --- a/Tests/Packet++Test/TestDefinition.h +++ b/Tests/Packet++Test/TestDefinition.h @@ -258,3 +258,7 @@ PTF_TEST_CASE(SmtpEditTests); // Implemented in Asn1Tests.cpp PTF_TEST_CASE(Asn1DecodingTest); PTF_TEST_CASE(Asn1EncodingTest); + +// Implemented in LdapTests.cpp +PTF_TEST_CASE(LdapParsingTest); +PTF_TEST_CASE(LdapCreationTest); diff --git a/Tests/Packet++Test/Tests/Asn1Tests.cpp b/Tests/Packet++Test/Tests/Asn1Tests.cpp index 161516c783..7b650f86f9 100644 --- a/Tests/Packet++Test/Tests/Asn1Tests.cpp +++ b/Tests/Packet++Test/Tests/Asn1Tests.cpp @@ -340,7 +340,7 @@ PTF_TEST_CASE(Asn1DecodingTest) PTF_TEST_CASE(Asn1EncodingTest) { - // Generic record + // Generic record with byte array value { uint8_t value[] = {0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x63, 0x6c, 0x61, 0x73, 0x73}; pcpp::Asn1GenericRecord record(pcpp::Asn1TagClass::ContextSpecific, false, 7, value, 11); @@ -361,6 +361,26 @@ PTF_TEST_CASE(Asn1EncodingTest) PTF_ASSERT_BUF_COMPARE(encodedValue.data(), data, dataLen) } + // Generic record with string value + { + pcpp::Asn1GenericRecord record(pcpp::Asn1TagClass::ContextSpecific, false, 7, "objectclass"); + + PTF_ASSERT_EQUAL(record.getTagClass(), pcpp::Asn1TagClass::ContextSpecific, enumclass); + PTF_ASSERT_FALSE(record.isConstructed()); + PTF_ASSERT_EQUAL(record.getUniversalTagType(), pcpp::Asn1UniversalTagType::NotApplicable, enumclass); + PTF_ASSERT_EQUAL(record.getTotalLength(), 13); + PTF_ASSERT_EQUAL(record.getValueLength(), 11); + auto recordValue = std::string(record.getValue(), record.getValue() + record.getValueLength()); + PTF_ASSERT_EQUAL(recordValue, "objectclass"); + + uint8_t data[20]; + auto dataLen = pcpp::hexStringToByteArray("870b6f626a656374636c617373", data, 20); + + auto encodedValue = record.encode(); + PTF_ASSERT_EQUAL(encodedValue.size(), dataLen); + PTF_ASSERT_BUF_COMPARE(encodedValue.data(), data, dataLen) + } + // Long length { pcpp::Asn1OctetStringRecord record("12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"); diff --git a/Tests/Packet++Test/Tests/LdapTests.cpp b/Tests/Packet++Test/Tests/LdapTests.cpp new file mode 100644 index 0000000000..3d2e84d500 --- /dev/null +++ b/Tests/Packet++Test/Tests/LdapTests.cpp @@ -0,0 +1,169 @@ +#include "../TestDefinition.h" +#include "../Utils/TestUtils.h" +#include "Packet.h" +#include "SystemUtils.h" +#include "LdapLayer.h" +#include +#include + +PTF_TEST_CASE(LdapParsingTest) +{ + timeval time; + gettimeofday(&time, nullptr); + + // LDAP packet + { + READ_FILE_AND_CREATE_PACKET(1, "PacketExamples/ldap_add_response.dat"); + pcpp::Packet ldapPacket(&rawPacket1); + + auto ldapLayer = ldapPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(ldapLayer); + + PTF_ASSERT_EQUAL(ldapLayer->getMessageID(), 27); + PTF_ASSERT_EQUAL(ldapLayer->getLdapOperationType(), pcpp::LdapOperationType::AddResponse, enum); + PTF_ASSERT_EQUAL(ldapLayer->getProtocol(), pcpp::LDAP); + PTF_ASSERT_EQUAL(ldapLayer->getHeaderLen(), 14); + PTF_ASSERT_TRUE(ldapLayer->getControls().empty()); + PTF_ASSERT_EQUAL(ldapLayer->toString(), "LDAP Layer, AddResponse"); + + pcpp::Asn1IntegerRecord messageIdRecord(27); + + pcpp::Asn1EnumeratedRecord enumeratedRecord(0); + pcpp::Asn1OctetStringRecord stringRecord1(""); + pcpp::Asn1OctetStringRecord stringRecord2(""); + pcpp::Asn1ConstructedRecord expectedOperationRecord(pcpp::Asn1TagClass::Application, 9, {&enumeratedRecord, &stringRecord1, &stringRecord2}); + + pcpp::Asn1SequenceRecord expectedRootRecord({&messageIdRecord, &expectedOperationRecord}); + + PTF_ASSERT_EQUAL(ldapLayer->getRootAsn1Record()->toString(), expectedRootRecord.toString()); + PTF_ASSERT_EQUAL(ldapLayer->getLdapOperationAsn1Record()->toString(), expectedOperationRecord.toString()); + } + + // LDAP with multiple controls + { + READ_FILE_AND_CREATE_PACKET_LINKTYPE(1, "PacketExamples/ldap_search_request1.dat", pcpp::LINKTYPE_LINUX_SLL); + pcpp::Packet ldapWithControlsPacket(&rawPacket1); + + auto ldapLayer = ldapWithControlsPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(ldapLayer); + + auto controls = ldapLayer->getControls(); + std::vector expectedControls = { + {"1.2.840.113556.1.4.801", "3003020107"}, + {"1.2.840.113556.1.4.319", "3006020201f40400"} + }; + PTF_ASSERT_VECTORS_EQUAL(controls, expectedControls); + } + + // LDAP with partial controls + { + READ_FILE_AND_CREATE_PACKET(1, "PacketExamples/ldap_bind_request1.dat"); + pcpp::Packet ldapWithControlsPacket(&rawPacket1); + + auto ldapLayer = ldapWithControlsPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(ldapLayer); + + auto controls = ldapLayer->getControls(); + std::vector expectedControls = {{"1.3.6.1.4.1.42.2.27.8.5.1"}}; + PTF_ASSERT_VECTORS_EQUAL(controls, expectedControls); + } + + // LdapLayer tryGet + { + READ_FILE_AND_CREATE_PACKET(1, "PacketExamples/ldap_bind_request1.dat"); + buffer1[68] = 0x04; + pcpp::Packet malformedLdapPacket(&rawPacket1); + + auto malformedLdapLayer = malformedLdapPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(malformedLdapLayer); + + uint16_t messageId; + PTF_ASSERT_FALSE(malformedLdapLayer->tryGet(&pcpp::LdapLayer::getMessageID, messageId)); + + std::vector controls; + PTF_ASSERT_TRUE(malformedLdapLayer->tryGet(&pcpp::LdapLayer::getControls, controls)); + std::vector expectedControls = {{"1.3.6.1.4.1.42.2.27.8.5.1"}}; + PTF_ASSERT_VECTORS_EQUAL(controls, expectedControls); + } + + // Multiple LDAP messages in the same packet + { + READ_FILE_AND_CREATE_PACKET_LINKTYPE(1, "PacketExamples/ldap_multiple_messages.dat", pcpp::LINKTYPE_LINUX_SLL); + pcpp::Packet multipleLdapPacket(&rawPacket1); + + auto ldapLayer = multipleLdapPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(ldapLayer); + + for (int i = 0; i < 3; i++) + { + PTF_ASSERT_EQUAL(ldapLayer->getLdapOperationType(), pcpp::LdapOperationType::SearchResultReference, enum); + ldapLayer = dynamic_cast(ldapLayer->getNextLayer()); + PTF_ASSERT_NOT_NULL(ldapLayer); + } + + PTF_ASSERT_EQUAL(ldapLayer->getLdapOperationType(), pcpp::LdapOperationType::SearchResultDone, enum); + PTF_ASSERT_NULL(ldapLayer->getNextLayer()); + } +} // LdapParsingTest + + +PTF_TEST_CASE(LdapCreationTest) +{ + timeval time; + gettimeofday(&time, nullptr); + + // LDAP packet with multiple controls + { + READ_FILE_AND_CREATE_PACKET_LINKTYPE(1, "PacketExamples/ldap_search_request1.dat", pcpp::LINKTYPE_LINUX_SLL); + pcpp::Packet ldapPacket(&rawPacket1); + + pcpp::Asn1OctetStringRecord stringRecord("DC=matrix,DC=local"); + pcpp::Asn1EnumeratedRecord enumeratedRecord1(2); + pcpp::Asn1EnumeratedRecord enumeratedRecord2(3); + pcpp::Asn1IntegerRecord integerRecord1(0); + pcpp::Asn1IntegerRecord integerRecord2(0); + pcpp::Asn1BooleanRecord booleanRecord(false); + + pcpp::Asn1GenericRecord subRecord1(pcpp::Asn1TagClass::ContextSpecific, false, 1, "2.16.840.1.113730.3.3.2.46.1"); + pcpp::Asn1GenericRecord subRecord2(pcpp::Asn1TagClass::ContextSpecific, false, 2, "departmentNumber"); + pcpp::Asn1GenericRecord subRecord3(pcpp::Asn1TagClass::ContextSpecific, false, 3, ">=N4709"); + pcpp::Asn1ConstructedRecord constructedRecord1(pcpp::Asn1TagClass::ContextSpecific, 9, {&subRecord1, &subRecord2, &subRecord3}); + + pcpp::Asn1OctetStringRecord stringSubRecord1("*"); + pcpp::Asn1OctetStringRecord stringSubRecord2("ntsecuritydescriptor"); + pcpp::Asn1SequenceRecord sequenceRecord({&stringSubRecord1, &stringSubRecord2}); + + std::vector controls = { + {"1.2.840.113556.1.4.801", "3003020107"}, + {"1.2.840.113556.1.4.319", "3006020201f40400"} + }; + + pcpp::LdapLayer ldapLayer(6, pcpp::LdapOperationType::SearchRequest, + {&stringRecord, &enumeratedRecord1, &enumeratedRecord2, &integerRecord1, &integerRecord2, &booleanRecord, &constructedRecord1, &sequenceRecord}, + controls); + + auto expectedLdapLayer = ldapPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(expectedLdapLayer); + + PTF_ASSERT_BUF_COMPARE(ldapLayer.getData(), expectedLdapLayer->getData(), expectedLdapLayer->getDataLen()); + } + + // LDAP packet with partial controls + { + READ_FILE_AND_CREATE_PACKET(1, "PacketExamples/ldap_bind_request1.dat"); + pcpp::Packet ldapPacket(&rawPacket1); + + pcpp::Asn1IntegerRecord integerRecord(3); + pcpp::Asn1OctetStringRecord stringRecord("cn=Administrator,cn=Users,dc=cloudshark-a,dc=example,dc=com"); + uint8_t contextSpecificData[14] = {0x63, 0x6c, 0x6f, 0x75, 0x64, 0x73, 0x68, 0x61, 0x72, 0x6b, 0x31, 0x32, 0x33, 0x21}; + pcpp::Asn1GenericRecord contextSpecificRecord(pcpp::Asn1TagClass::ContextSpecific, false, 0, contextSpecificData, 14); + std::vector controls = {{"1.3.6.1.4.1.42.2.27.8.5.1"}}; + + pcpp::LdapLayer ldapLayer(2, pcpp::LdapOperationType::BindRequest, {&integerRecord, &stringRecord, &contextSpecificRecord}, controls); + + auto expectedLdapLayer = ldapPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(expectedLdapLayer); + + PTF_ASSERT_BUF_COMPARE(ldapLayer.getData(), expectedLdapLayer->getData(), expectedLdapLayer->getDataLen()); + } +} // LdapCreationTest diff --git a/Tests/Packet++Test/main.cpp b/Tests/Packet++Test/main.cpp index 0aee7ae37d..937bf62707 100644 --- a/Tests/Packet++Test/main.cpp +++ b/Tests/Packet++Test/main.cpp @@ -328,5 +328,8 @@ int main(int argc, char* argv[]) PTF_RUN_TEST(Asn1DecodingTest, "asn1"); PTF_RUN_TEST(Asn1EncodingTest, "asn1"); + PTF_RUN_TEST(LdapParsingTest, "ldap"); + PTF_RUN_TEST(LdapCreationTest, "ldap"); + PTF_END_RUNNING_TESTS; } diff --git a/Tests/PcppTestFramework/PcppTestFramework.h b/Tests/PcppTestFramework/PcppTestFramework.h index 8f63774710..648dceeb73 100644 --- a/Tests/PcppTestFramework/PcppTestFramework.h +++ b/Tests/PcppTestFramework/PcppTestFramework.h @@ -75,6 +75,30 @@ } \ } +#define PTF_ASSERT_VECTORS_EQUAL(actual, expected, ...) \ + { \ + if (actual != expected) {\ + std::ostringstream actualOss, expectedOss; \ + bool first = true; \ + for (const auto& elem : actual) { \ + if (!first) actualOss << ", "; \ + actualOss << elem; \ + first = false; \ + } \ + first = true; \ + for (const auto& elem : expected) { \ + if (!first) expectedOss << ", "; \ + expectedOss << elem; \ + first = false; \ + } \ + std::string actualValues = "[" + actualOss.str() + "]"; \ + std::string expectedValues = "[" + expectedOss.str() + "]"; \ + PTF_PRINT_COMPARE_ASSERTION_FAILED("VECTORS EQUAL", #actual, actualValues, #expected, expectedValues, __VA_ARGS__); \ + ptfResult = PTF_RESULT_FAILED; \ + return; \ + } \ + } + #define PTF_ASSERT_NOT_EQUAL(actual, expected, ...) \ { \ auto ptfActual = actual; \