From: Mike Stepanek (mstepane) Date: Wed, 21 Jul 2021 15:51:19 +0000 (+0000) Subject: Merge pull request #2974 in SNORT/snort3 from ~YVELYKOZ/snort3:ips_byte_options_updat... X-Git-Tag: 3.1.9.0~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b34a8fe74a4b174913e4ee10af4ae2b11a4672c8;p=thirdparty%2Fsnort3.git Merge pull request #2974 in SNORT/snort3 from ~YVELYKOZ/snort3:ips_byte_options_update to master Squashed commit of the following: commit acf28ee21eba548ff0eae5119e57eb70683c52d7 Author: VytalyGorbatov Date: Mon May 17 09:14:21 2021 +0300 ips_options: refactor byte_extract, byte_test, byte_math, byte_jump and related tests Move common logic to extract.cc. commit 65523a0c2b8c6c8ac7dc6629ed7ab697776642d5 Author: Crowy-o Date: Thu May 13 14:22:34 2021 +0300 ips_options: add catch tests for byte_test, byte_jump, byte_math, byte_extract --- diff --git a/src/framework/ips_option.cc b/src/framework/ips_option.cc index 8c8d9eb5d..6d68c7dad 100644 --- a/src/framework/ips_option.cc +++ b/src/framework/ips_option.cc @@ -65,3 +65,82 @@ bool IpsOption::operator==(const IpsOption& ips) const !strcmp(get_buffer(), ips.get_buffer()); } +//------------------------------------------------------------------------- +// UNIT TESTS +//------------------------------------------------------------------------- +#ifdef UNIT_TEST +#include "catch/snort_catch.h" + +class StubIpsOption : public IpsOption +{ +public: + StubIpsOption(const char* name, option_type_t option_type) : + IpsOption(name, option_type) + { } +}; + +TEST_CASE("IpsOption test", "[ips_option]") +{ + StubIpsOption main_ips("ips_test", + option_type_t::RULE_OPTION_TYPE_OTHER); + + SECTION("IpsOperator == test") + { + StubIpsOption case_diff_name("not_hello_world", + option_type_t::RULE_OPTION_TYPE_BUFFER_USE); + REQUIRE((main_ips == case_diff_name) == false); + + StubIpsOption case_diff_option("hello_world", + option_type_t::RULE_OPTION_TYPE_CONTENT); + REQUIRE((main_ips == case_diff_option) == false); + + StubIpsOption case_option_na("hello_world", + option_type_t::RULE_OPTION_TYPE_OTHER); + REQUIRE((main_ips == case_option_na) == false); + } + + SECTION("hash test") + { + StubIpsOption main_ips("ips_test", + option_type_t::RULE_OPTION_TYPE_OTHER); + + SECTION("hash test with short string") + { + StubIpsOption main_ips_short("ips_test", + option_type_t::RULE_OPTION_TYPE_OTHER); + REQUIRE((main_ips.hash() == main_ips_short.hash()) == true); + + StubIpsOption main_ips_short_diff("not_ips_test", + option_type_t::RULE_OPTION_TYPE_OTHER); + REQUIRE((main_ips.hash() == main_ips_short_diff.hash()) == false); + } + + SECTION("hash test with long string") + { + std::string really_long_string = + "101010101010101010101010101010101010101010101010101010101010101" \ + "101010101010101010101010101010101010101010101010101010101010101" \ + "101010101010101010101010101010101010101010101010101010101010101" \ + "101010101010101010101010101010101010101010101010101010101010101" \ + "101010101010101010101010101010101010101010101010101010101010101" \ + "101010101010101010101010101010101010101010101010101010101010101" \ + "101010101010101010101010101010101010101010101010101010101010101" \ + "101010101010101010101010101010101010101010101010101010101010101" \ + "101010101010101010101010101010101010101010101010101010101010101" \ + "101010101010101010101010101010101010101010101010101010101010101" \ + "101010101010101010101010101010101010101010101010101010101010101" \ + "101010101010101010101010101010101010101010101010101010101010101" \ + "101010101010101010101010101010101010101010101010101010101010101"; + + StubIpsOption main_ips_long_first(really_long_string.c_str(), + option_type_t::RULE_OPTION_TYPE_OTHER); + StubIpsOption main_ips_long_second(really_long_string.c_str(), + option_type_t::RULE_OPTION_TYPE_OTHER); + REQUIRE(main_ips_long_first.hash() == main_ips_long_second.hash()); + + REQUIRE(main_ips_long_first.hash() != main_ips.hash()); + } + } +} + +#endif diff --git a/src/ips_options/CMakeLists.txt b/src/ips_options/CMakeLists.txt index 1db3c18d6..bc482d8b0 100644 --- a/src/ips_options/CMakeLists.txt +++ b/src/ips_options/CMakeLists.txt @@ -101,10 +101,10 @@ else (STATIC_IPS_OPTIONS) add_dynamic_module(ips_ber_data ips_options ips_ber_data.cc) add_dynamic_module(ips_ber_skip ips_options ips_ber_skip.cc) add_dynamic_module(ips_bufferlen ips_options ips_bufferlen.cc) - add_dynamic_module(ips_byte_extract ips_options ips_byte_extract.cc) - add_dynamic_module(ips_byte_jump ips_options ips_byte_jump.cc) - add_dynamic_module(ips_byte_math ips_options ips_byte_math.cc) - add_dynamic_module(ips_byte_test ips_options ips_byte_test.cc) + add_dynamic_module(ips_byte_extract ips_options extract.cc ips_byte_extract.cc) + add_dynamic_module(ips_byte_jump ips_options extract.cc ips_byte_jump.cc) + add_dynamic_module(ips_byte_math ips_options extract.cc ips_byte_math.cc) + add_dynamic_module(ips_byte_test ips_options extract.cc ips_byte_test.cc) add_dynamic_module(ips_cvs ips_options ips_cvs.cc) add_dynamic_module(ips_enable ips_options ips_enable.cc) add_dynamic_module(ips_file_type ips_options ips_file_type.cc) diff --git a/src/ips_options/extract.cc b/src/ips_options/extract.cc index c5c1ec418..73195a431 100644 --- a/src/ips_options/extract.cc +++ b/src/ips_options/extract.cc @@ -27,6 +27,7 @@ #include "extract.h" +#include "framework/ips_option.h" #include "log/messages.h" #include "utils/snort_bounds.h" #include "utils/util_cstring.h" @@ -34,6 +35,7 @@ #ifdef UNIT_TEST #include "catch/snort_catch.h" +#include "service_inspectors/dce_rpc/dce_common.h" #endif using namespace snort; @@ -46,6 +48,7 @@ static THREAD_LOCAL uint8_t extracted_values_cnt = 0; namespace snort { + /* Given a variable name, retrieve its index.*/ int8_t GetVarByName(const char* name) { @@ -96,7 +99,7 @@ void ClearIpsOptionsVars() used at this point */ int GetVarValueByIndex(uint32_t* dst, uint8_t var_number) { - if (dst == nullptr || var_number >= NUM_IPS_OPTIONS_VARS) + if (dst == nullptr or var_number >= NUM_IPS_OPTIONS_VARS) return IPS_OPTIONS_NO_VAR; *dst = extracted_values[var_number]; @@ -116,7 +119,7 @@ int SetVarValueByIndex(uint32_t value, uint8_t var_number) void set_byte_order(uint8_t& order, uint8_t flag, const char* opt) { - if ( order ) + if (order) ParseWarning(WARN_RULES, "%s specifies multiple byte orders, using last", opt); order = flag; @@ -143,22 +146,15 @@ int byte_extract(int endianness, int bytes_to_grab, const uint8_t* ptr, const uint8_t* start, const uint8_t* end, uint32_t* value) { - if (endianness != ENDIAN_LITTLE && endianness != ENDIAN_BIG) - { - /* we only support 2 byte formats */ - return -2; - } + if (endianness != ENDIAN_LITTLE and endianness != ENDIAN_BIG) + return -2; /* we only support 2 byte formats */ /* make sure the data to grab stays in bounds */ if (!inBounds(start,end,ptr + (bytes_to_grab - 1))) - { return -3; - } if (!inBounds(start,end,ptr)) - { return -3; - } /* * We only support grabbing 1, 2, or 4 bytes of binary data. @@ -232,33 +228,24 @@ int byte_extract(int endianness, int bytes_to_grab, const uint8_t* ptr, * @returns 0 on success, otherwise failure */ int string_extract(int bytes_to_grab, int base, const uint8_t* ptr, - const uint8_t* start, const uint8_t* end, - uint32_t* value) + const uint8_t* start, const uint8_t* end, uint32_t* value) { char byte_array[TEXTLEN]; char* parse_helper; int x; /* counter */ - if (bytes_to_grab > (TEXTLEN - 1) || bytes_to_grab <= 0) - { + if (bytes_to_grab > (TEXTLEN - 1) or bytes_to_grab <= 0) return -1; - } /* make sure the data to grab stays in bounds */ if (!inBounds(start,end,ptr + (bytes_to_grab - 1))) - { return -3; - } if (!inBounds(start,end,ptr)) - { return -3; - } for (x=0; x= end) + return IpsOption::NO_MATCH; + + uint8_t endian = settings.endianness; + if (settings.endianness == ENDIAN_FUNC) + { + if (!p->endianness or + !p->endianness->get_offset_endianness(ptr - p->data, endian)) + return IpsOption::NO_MATCH; + } + + // do the extraction + int32_t bytes_read = 0; + uint32_t value = 0; + if (!settings.string_convert_flag) + { + int ret = 0; + ret = byte_extract(endian, settings.bytes_to_extract, ptr, start, end, &value); + if (ret < 0) + return IpsOption::NO_MATCH; + + bytes_read = settings.bytes_to_extract; + } + else + { + bytes_read = string_extract(settings.bytes_to_extract, settings.base, + ptr, start, end, &value); + if (bytes_read < 0) + return IpsOption::NO_MATCH; + } + + if (settings.bitmask_val != 0) + { + uint32_t num_tailing_zeros_bitmask = + getNumberTailingZerosInBitmask(settings.bitmask_val); + value = value & settings.bitmask_val; + if (value and num_tailing_zeros_bitmask) + value = value >> num_tailing_zeros_bitmask; + } + + result_var = value; + return bytes_read; +} + +int32_t extract_data(const ByteData& settings, const Cursor& c, Packet* p, + uint32_t& result_var) +{ + const uint8_t* start = nullptr; + const uint8_t* ptr = nullptr; + const uint8_t* end = nullptr; + set_cursor_bounds(settings, c, start, ptr, end); + return data_extraction(settings, p, result_var, start, ptr, end); } uint32_t getNumberTailingZerosInBitmask(uint32_t bitmask) @@ -287,11 +347,11 @@ uint32_t getNumberTailingZerosInBitmask(uint32_t bitmask) uint8_t numBytesInBitmask(uint32_t bitmask_value) { uint8_t num_bytes; - if ( bitmask_value <= 0xFF ) + if (bitmask_value <= 0xFF) num_bytes = 1; - else if ( bitmask_value <= 0xFFFF ) + else if (bitmask_value <= 0xFFFF) num_bytes = 2; - else if ( bitmask_value <= 0xFFFFFF ) + else if (bitmask_value <= 0xFFFFFF) num_bytes = 3; else num_bytes = 4; @@ -301,7 +361,21 @@ uint8_t numBytesInBitmask(uint32_t bitmask_value) } // namespace snort +//------------------------------------------------------------------------- +// UNIT TESTS +//------------------------------------------------------------------------- #ifdef UNIT_TEST + +#define INITIALIZE(obj, bytes_to_extract_value, offset_value, relative_flag_value, \ + string_convert_flag_value, base_value, endianness_value, bitmask_val_value) \ + obj.base = base_value; \ + obj.bitmask_val = bitmask_val_value; \ + obj.bytes_to_extract = bytes_to_extract_value; \ + obj.offset = offset_value; \ + obj.endianness = endianness_value; \ + obj.relative_flag = relative_flag_value; \ + obj.string_convert_flag = string_convert_flag_value; + TEST_CASE("ips options bitmask utils") { // numBytesInBitmask tests @@ -326,8 +400,10 @@ TEST_CASE("ips options vars") // Fill up array int8_t ind1 = AddVarNameToList("OFFSET"); REQUIRE((ind1 == 0)); + int8_t ind2 = AddVarNameToList("VALUE"); REQUIRE((ind2 == 1)); + int8_t ind3 = AddVarNameToList("VAR3"); REQUIRE((ind3 == IPS_OPTIONS_NO_VAR)); @@ -347,5 +423,273 @@ TEST_CASE("ips options vars") REQUIRE((GetVarValueByIndex(&dst, NUM_IPS_OPTIONS_VARS) == IPS_OPTIONS_NO_VAR)); REQUIRE((SetVarValueByIndex(0, NUM_IPS_OPTIONS_VARS) == IPS_OPTIONS_NO_VAR)); } -#endif +TEST_CASE("set_cursor_bounds", "[byte_extraction_tests]") +{ + Packet p; + p.data = (const uint8_t*)"Lorem 010 12345 0x75"; + p.dsize = 21; + Cursor c(&p); + const uint8_t* start = nullptr; + const uint8_t* ptr = nullptr; + const uint8_t* end = nullptr; + + SECTION("4 bytes read, no offset") + { + ByteData settings; + INITIALIZE(settings, 4, 0, 0, 0, 0, ENDIAN_BIG, 0); + set_cursor_bounds(settings, c, start, ptr, end); + CHECK(start == p.data); + CHECK(ptr == p.data); + CHECK(end == p.data + 21); + } + SECTION("4 byte read, offset = 4") + { + ByteData settings; + INITIALIZE(settings, 4, 4, 0, 0, 0, ENDIAN_BIG, 0); + set_cursor_bounds(settings, c, start, ptr, end); + CHECK(start == p.data); + CHECK(ptr == p.data + 4); + CHECK(end == p.data + 21); + } + SECTION("4 bytes read, cursor move without relative flag") + { + c.set_pos(3); + ByteData settings; + INITIALIZE(settings, 4, 0, 0, 0, 0, ENDIAN_BIG, 0); + set_cursor_bounds(settings, c, start, ptr, end); + CHECK(start == p.data); + CHECK(ptr == p.data); + CHECK(end == p.data + 21); + } + SECTION("4 bytes read, cursor move with relative flag") + { + c.set_pos(3); + ByteData settings; + INITIALIZE(settings, 4, 0, true, 0, 0, ENDIAN_BIG, 0); + set_cursor_bounds(settings, c, start, ptr, end); + CHECK(start == p.data); + CHECK(ptr == p.data + 3); + CHECK(end == p.data + 21); + } +} + +TEST_CASE("extract_data valid", "[byte_extraction_tests]") +{ + Packet p; + p.data = (const uint8_t*)"Lorem 010 12345 0x75"; + p.dsize = 21; + Cursor c(&p); + uint32_t res = 0; + + SECTION("1 byte read, all - off") + { + ByteData settings; + INITIALIZE(settings, 1, 0, 0, 0, 0, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c, &p, res) == 1); + CHECK(res == 76); + } + SECTION("2 bytes read, all - off") + { + ByteData settings; + INITIALIZE(settings, 2, 0, 0, 0, 0, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c, &p, res) == 2); + CHECK(res == 19567); + } + SECTION("3 bytes read, all - off") + { + ByteData settings; + INITIALIZE(settings, 3, 0, 0, 0, 0, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c, &p, res) == 3); + CHECK(res == 5009266); + } + SECTION("4 bytes read, all - off") + { + ByteData settings; + INITIALIZE(settings, 4, 0, 0, 0, 0, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c, &p, res) == 4); + CHECK(res == 1282372197); + } + SECTION("1 byte read, offset 3") + { + ByteData settings; + INITIALIZE(settings, 1, 3, 0, 0, 0, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c, &p, res) == 1); + CHECK(res == 101); + } + SECTION("1 byte read, offset 3, relative") + { + ByteData settings; + INITIALIZE(settings, 1, 3, 1, 0, 0, ENDIAN_BIG, 0); + c.set_pos(3); + CHECK(extract_data(settings, c, &p, res) == 1); + CHECK(res == 48); + } + SECTION("cursor 3, 1 byte read, offset -3, relative") + { + ByteData settings; + INITIALIZE(settings, 1, -3, 1, 0, 0, ENDIAN_BIG, 0); + c.set_pos(3); + CHECK(extract_data(settings, c, &p, res) == 1); + CHECK(res == 76); + } + SECTION("1 byte read, offset 6, string conversion, base 10") + { + ByteData settings; + INITIALIZE(settings, 1, 10, 0, 1, 10, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c, &p, res) == 1); + CHECK(res == 1); + } + SECTION("2 bytes read, offset 6, string conversion, base 8 without prefix") + { + ByteData settings; + INITIALIZE(settings, 2, 10, 0, 1, 8, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c, &p, res) == 2); + CHECK(res == 10); + } + SECTION("2 bytes read, offset 6, string conversion, base 10") + { + ByteData settings; + INITIALIZE(settings, 2, 10, 0, 1, 10, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c, &p, res) == 2); + CHECK(res == 12); + } + SECTION("2 bytes read, offset 6, string conversion, base 16 without prefix") + { + ByteData settings; + INITIALIZE(settings, 2, 10, 0, 1, 16, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c, &p, res) == 2); + CHECK(res == 18); + } + SECTION("3 bytes read, offset 6, string conversion, base 8 with prefix") + { + ByteData settings; + INITIALIZE(settings, 3, 6, 0, 1, 8, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c, &p, res) == 3); + CHECK(res == 8); + } + SECTION("4 bytes read, offset 6, string conversion, base 16 with prefix") + { + ByteData settings; + INITIALIZE(settings, 4, 16, 0, 1, 16, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c, &p, res) == 4); + CHECK(res == 117); + } + SECTION("2 byte read, bitmask 1100110011100011") + { + ByteData settings; + INITIALIZE(settings, 2, 0, 0, 0, 0, ENDIAN_BIG, 52451); + CHECK(extract_data(settings, c, &p, res) == 2); + CHECK(res == 19555); + } + SECTION("2 byte read, bitmask 1100110011100000") + { + ByteData settings; + INITIALIZE(settings, 2, 0, 0, 0, 0, ENDIAN_BIG, 52448); + CHECK(extract_data(settings, c, &p, res) == 2); + CHECK(res == 611); + } + SECTION("4 bytes read, ENDIAN_LITTLE") + { + ByteData settings; + INITIALIZE(settings, 4, 0, 0, 0, 0, ENDIAN_LITTLE, 0); + CHECK(extract_data(settings, c, &p, res) == 4); + CHECK(res == 1701998412); + } + SECTION("4 bytes read, ENDIAN_FUNC, packet.endianness " \ + "= DCERPC_BO_FLAG__LITTLE_ENDIAN") + { + DceEndianness* auto_endian = new DceEndianness(); + auto_endian->hdr_byte_order = DCERPC_BO_FLAG__LITTLE_ENDIAN; + auto_endian->data_byte_order = DCERPC_BO_FLAG__LITTLE_ENDIAN; + p.endianness = auto_endian; + ByteData settings; + INITIALIZE(settings, 4, 0, 0, 0, 0, ENDIAN_FUNC, 0); + CHECK(extract_data(settings, c, &p, res) == 4); + CHECK(res == 1701998412); + } +} + +TEST_CASE("extract_data invalid", "[byte_extraction_tests]") +{ + Packet p; + p.data = (const uint8_t*)"Lorem 9876"; + p.dsize = 11; + Cursor c(&p); + uint32_t res = 0; + + SECTION("packet = nullptr") + { + ByteData settings; + INITIALIZE(settings, 1, 0, 0, 0, 0, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c, nullptr, res) == IpsOption::NO_MATCH); + } + SECTION("read more than 4 bytes") + { + ByteData settings; + INITIALIZE(settings, 6, 0, 0, 0, 0, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c, &p, res) == IpsOption::NO_MATCH); + } + SECTION("check bounds of packet, offset > packet size") + { + ByteData settings; + INITIALIZE(settings, 1, 20, 0, 0, 0, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c, &p, res) == IpsOption::NO_MATCH); + } + SECTION("negative offset, without relative flag") + { + ByteData settings; + INITIALIZE(settings, 1, -20, 0, 0, 0, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c, &p, res) == IpsOption::NO_MATCH); + } + SECTION("negative offset, out of bounds") + { + ByteData settings; + INITIALIZE(settings, 1, -20, 1, 0, 0, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c, &p, res) == IpsOption::NO_MATCH); + } + SECTION("check bounds of packet, offset > packet size, empty packet") + { + p.data = (const uint8_t*)""; + p.dsize = 0; + Cursor c2(&p); + ByteData settings; + INITIALIZE(settings, 1, 20, 0, 0, 0, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c2, &p, res) == IpsOption::NO_MATCH); + } + SECTION("check bounds of packet, read 2 bytes, empty packet") + { + p.data = (const uint8_t*)""; + p.dsize = 0; + Cursor c2(&p); + ByteData settings; + INITIALIZE(settings, 2, 0, 0, 0, 0, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c2, &p, res) == IpsOption::NO_MATCH); + } + SECTION("ENDIAN_FUNC, without definition of endianness in packet") + { + ByteData settings; + INITIALIZE(settings, 3, 0, 0, 0, 0, ENDIAN_FUNC, 0); + CHECK(extract_data(settings, c, &p, res) == IpsOption::NO_MATCH); + } + SECTION("conversion from string, decimal number, base = 8") + { + ByteData settings; + INITIALIZE(settings, 3, 6, 0, 1, 8, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c, &p, res) == IpsOption::NO_MATCH); + } + SECTION("conversion from string but the input is symbol") + { + ByteData settings; + INITIALIZE(settings, 1, 0, 0, 1, 10, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c, &p, res) == IpsOption::NO_MATCH); + } + SECTION("cursor behind the packet size") + { + c.set_pos(15); + ByteData settings; + INITIALIZE(settings, 1, 0, 0, 1, 10, ENDIAN_BIG, 0); + CHECK(extract_data(settings, c, &p, res) == IpsOption::NO_MATCH); + } +} +#endif diff --git a/src/ips_options/extract.h b/src/ips_options/extract.h index 19ed85627..918399ffb 100644 --- a/src/ips_options/extract.h +++ b/src/ips_options/extract.h @@ -20,8 +20,11 @@ #ifndef EXTRACT_H #define EXTRACT_H -#include "main/thread.h" +#include "framework/cursor.h" +#include "framework/endianness.h" #include "main/snort_types.h" +#include "main/thread.h" +#include "protocols/packet.h" #define ENDIAN_BIG 0x1 #define ENDIAN_LITTLE 0x2 @@ -36,6 +39,18 @@ namespace snort { + +struct ByteData +{ + uint32_t base; + uint32_t bitmask_val; + uint32_t bytes_to_extract; + int32_t offset; + uint8_t endianness; + bool relative_flag; + bool string_convert_flag; +}; + SO_PUBLIC int string_extract( int bytes_to_grab, int base, const uint8_t* ptr, const uint8_t* start, const uint8_t* end, uint32_t* value); @@ -44,6 +59,16 @@ SO_PUBLIC int byte_extract( int endianness, int bytes_to_grab, const uint8_t* ptr, const uint8_t* start, const uint8_t* end, uint32_t* value); +void set_cursor_bounds(const ByteData& settings, const Cursor& c, + const uint8_t*& start, const uint8_t*& ptr, const uint8_t*& end); + +int32_t data_extraction(const ByteData& settings, Packet* p, + uint32_t& result_var, const uint8_t* start, + const uint8_t* ptr, const uint8_t* end); + +int32_t extract_data(const ByteData& settings, const Cursor& c, Packet* p, + uint32_t& result_var); + SO_PUBLIC void set_byte_order(uint8_t& order, uint8_t flag, const char* opt); SO_PUBLIC uint32_t getNumberTailingZerosInBitmask(uint32_t); @@ -59,4 +84,3 @@ SO_PUBLIC int GetVarValueByIndex(uint32_t* dst, uint8_t var_number); SO_PUBLIC int SetVarValueByIndex(uint32_t value, uint8_t var_number); } #endif - diff --git a/src/ips_options/ips_byte_extract.cc b/src/ips_options/ips_byte_extract.cc index 26c2668e8..353d30e4d 100644 --- a/src/ips_options/ips_byte_extract.cc +++ b/src/ips_options/ips_byte_extract.cc @@ -29,12 +29,17 @@ #include "framework/module.h" #include "hash/hash_key_operations.h" #include "log/messages.h" -#include "protocols/packet.h" #include "profiler/profiler.h" +#include "protocols/packet.h" #include "utils/util.h" #include "extract.h" +#ifdef UNIT_TEST +#include +#include "service_inspectors/dce_rpc/dce_common.h" +#endif + using namespace snort; static THREAD_LOCAL ProfileStats byteExtractPerfStats; @@ -44,17 +49,10 @@ static THREAD_LOCAL ProfileStats byteExtractPerfStats; #define s_help \ "rule option to convert data to an integer variable" -struct ByteExtractData +struct ByteExtractData : public ByteData { - uint32_t bytes_to_grab; - int32_t offset; - uint8_t relative_flag; - uint8_t data_string_convert_flag; - uint8_t align; - uint8_t endianness; - uint32_t base; uint32_t multiplier; - uint32_t bitmask_val; + uint8_t align; int8_t var_number; char* name; }; @@ -62,8 +60,9 @@ struct ByteExtractData class ByteExtractOption : public IpsOption { public: - ByteExtractOption(const ByteExtractData& c) : IpsOption(s_name, RULE_OPTION_TYPE_BUFFER_USE) - { config = c; } + ByteExtractOption(const ByteExtractData& c) : + IpsOption(s_name, RULE_OPTION_TYPE_BUFFER_USE), config(c) + { } ~ByteExtractOption() override { snort_free(config.name); } @@ -81,6 +80,7 @@ public: private: ByteExtractData config; + void apply_alignment(uint32_t& value); }; //------------------------------------------------------------------------- @@ -89,14 +89,14 @@ private: uint32_t ByteExtractOption::hash() const { - uint32_t a = config.bytes_to_grab; + uint32_t a = config.bytes_to_extract; uint32_t b = config.offset; uint32_t c = config.base; mix(a,b,c); a += (config.relative_flag << 24 | - config.data_string_convert_flag << 16 | + config.string_convert_flag << 16 | config.align << 8 | config.endianness); b += config.multiplier; @@ -115,22 +115,22 @@ uint32_t ByteExtractOption::hash() const bool ByteExtractOption::operator==(const IpsOption& ips) const { - if ( !IpsOption::operator==(ips) ) + if (!IpsOption::operator==(ips)) return false; const ByteExtractOption& rhs = (const ByteExtractOption&)ips; const ByteExtractData* left = &config; const ByteExtractData* right = &rhs.config; - if ((left->bytes_to_grab == right->bytes_to_grab) && - (left->offset == right->offset) && - (left->relative_flag == right->relative_flag) && - (left->data_string_convert_flag == right->data_string_convert_flag) && - (left->align == right->align) && - (left->endianness == right->endianness) && - (left->base == right->base) && - (left->multiplier == right->multiplier) && - (left->var_number == right->var_number) && + if ((left->bytes_to_extract == right->bytes_to_extract) and + (left->offset == right->offset) and + (left->relative_flag == right->relative_flag) and + (left->string_convert_flag == right->string_convert_flag) and + (left->align == right->align) and + (left->endianness == right->endianness) and + (left->base == right->base) and + (left->multiplier == right->multiplier) and + (left->var_number == right->var_number) and (left->bitmask_val == right->bitmask_val)) { return true; @@ -143,84 +143,36 @@ IpsOption::EvalStatus ByteExtractOption::eval(Cursor& c, Packet* p) { RuleProfile profile(byteExtractPerfStats); - ByteExtractData* data = &config; + uint32_t value = 0; + int bytes_read = extract_data(config, c, p, value); - if (data == nullptr || p == nullptr) + if (bytes_read == NO_MATCH) return NO_MATCH; - const uint8_t* start = c.buffer(); - int dsize = c.size(); + value *= config.multiplier; - const uint8_t* ptr = data->relative_flag ? c.start() : c.buffer(); - ptr += data->offset; + apply_alignment(value); - const uint8_t* end = start + dsize; + SetVarValueByIndex(value, config.var_number); - // check bounds - if (ptr < start || ptr >= end) - return NO_MATCH; + c.add_pos(config.offset + bytes_read); - uint8_t endian = data->endianness; - if (data->endianness == ENDIAN_FUNC) - { - if (!p->endianness || - !p->endianness->get_offset_endianness(ptr - p->data, endian)) - return NO_MATCH; - } - - // do the extraction - int ret = 0; - int bytes_read = 0; - uint32_t value; - if (data->data_string_convert_flag == 0) - { - ret = byte_extract(endian, data->bytes_to_grab, ptr, start, end, &value); - if (ret < 0) - return NO_MATCH; - - bytes_read = data->bytes_to_grab; - } - else - { - ret = string_extract(data->bytes_to_grab, data->base, ptr, start, end, &value); - if (ret < 0) - return NO_MATCH; - - bytes_read = ret; - } - - if (data->bitmask_val != 0 ) - { - uint32_t num_tailing_zeros_bitmask = getNumberTailingZerosInBitmask(data->bitmask_val); - value = value & data->bitmask_val; - if ( value && num_tailing_zeros_bitmask ) - { - value = value >> num_tailing_zeros_bitmask; - } - } - - /* multiply */ - value *= data->multiplier; + return MATCH; +} - /* align to next 32-bit or 16-bit boundary */ - if ((data->align == 4) && (value % 4)) +void ByteExtractOption::apply_alignment(uint32_t& value) +{ + if ((config.align == 4) and (value % 4)) { value = value + 4 - (value % 4); } - else if ((data->align == 2) && (value % 2)) + else if ((config.align == 2) and (value % 2)) { value = value + 2 - (value % 2); } - - SetVarValueByIndex(value, data->var_number); - - /* advance cursor */ - c.add_pos(data->offset + bytes_read); - - /* this rule option always "matches" if the read is performed correctly */ - return MATCH; } + //------------------------------------------------------------------------- // api //------------------------------------------------------------------------- @@ -228,28 +180,28 @@ IpsOption::EvalStatus ByteExtractOption::eval(Cursor& c, Packet* p) /* Checks a ByteExtractData instance for errors. */ static bool ByteExtractVerify(ByteExtractData* data) { - if (data->bytes_to_grab > MAX_BYTES_TO_GRAB && data->data_string_convert_flag == 0) + if (data->bytes_to_extract > MAX_BYTES_TO_GRAB and data->string_convert_flag == 0) { ParseError("byte_extract rule option cannot extract more than %d bytes.", MAX_BYTES_TO_GRAB); return false; } - if (data->bytes_to_grab > PARSELEN && data->data_string_convert_flag == 1) + if (data->bytes_to_extract > PARSELEN and data->string_convert_flag == 1) { ParseError("byte_extract rule cannot process more than %d bytes for " "string extraction.", PARSELEN); return false; } - if (data->align != 0 && data->align != 2 && data->align != 4) + if (data->align != 0 and data->align != 2 and data->align != 4) { ParseError("byte_extract rule option has an invalid argument " "to 'align'. Valid arguments are '2' and '4'."); return false; } - if (data->offset < 0 && data->relative_flag == 0) + if (data->offset < 0 and data->relative_flag == 0) { ParseError("byte_extract rule option has a negative offset, but does " "not use the 'relative' option."); @@ -269,7 +221,7 @@ static bool ByteExtractVerify(ByteExtractData* data) return false; } - if (data->base && !data->data_string_convert_flag) + if (data->base and !data->string_convert_flag) { ParseError("byte_extract rule option has a string conversion type " "(dec, hex, or oct) without the \"string\" " @@ -277,7 +229,7 @@ static bool ByteExtractVerify(ByteExtractData* data) return false; } - if (numBytesInBitmask(data->bitmask_val) > data->bytes_to_grab) + if (numBytesInBitmask(data->bitmask_val) > data->bytes_to_extract) { ParseError("Number of bytes in \"bitmask\" value is greater than bytes to extract."); return false; @@ -353,7 +305,7 @@ public: { return DETECT; } public: - ByteExtractData data = {}; + ByteExtractData data{}; }; bool ExtractModule::begin(const char*, int, SnortConfig*) @@ -365,55 +317,55 @@ bool ExtractModule::begin(const char*, int, SnortConfig*) bool ExtractModule::end(const char*, int, SnortConfig*) { - if ( !data.endianness ) + if (!data.endianness) data.endianness = ENDIAN_BIG; return ByteExtractVerify(&data); } bool ExtractModule::set(const char*, Value& v, SnortConfig*) { - if ( v.is("~count") ) - data.bytes_to_grab = v.get_uint8(); + if (v.is("~count")) + data.bytes_to_extract = v.get_uint8(); - else if ( v.is("~offset") ) + else if (v.is("~offset")) data.offset = v.get_int32(); - else if ( v.is("~name") ) + else if (v.is("~name")) data.name = snort_strdup(v.get_string()); - else if ( v.is("relative") ) + else if (v.is("relative")) data.relative_flag = 1; - else if ( v.is("align") ) + else if (v.is("align")) data.align = v.get_uint8(); - else if ( v.is("multiplier") ) + else if (v.is("multiplier")) data.multiplier = v.get_uint16(); - else if ( v.is("big") ) + else if (v.is("big")) set_byte_order(data.endianness, ENDIAN_BIG, "byte_extract"); - else if ( v.is("little") ) + else if (v.is("little")) set_byte_order(data.endianness, ENDIAN_LITTLE, "byte_extract"); - else if ( v.is("dce") ) + else if (v.is("dce")) set_byte_order(data.endianness, ENDIAN_FUNC, "byte_extract"); - else if ( v.is("string") ) + else if (v.is("string")) { - data.data_string_convert_flag = 1; + data.string_convert_flag = 1; data.base = 10; } - else if ( v.is("dec") ) + else if (v.is("dec")) data.base = 10; - else if ( v.is("hex") ) + else if (v.is("hex")) data.base = 16; - else if ( v.is("oct") ) + else if (v.is("oct")) data.base = 8; - else if ( v.is("bitmask") ) + else if (v.is("bitmask")) data.bitmask_val = v.get_uint32(); else @@ -491,3 +443,694 @@ const BaseApi* ips_byte_extract[] = &byte_extract_api.base, nullptr }; + +//------------------------------------------------------------------------- +// UNIT TESTS +//------------------------------------------------------------------------- +#ifdef UNIT_TEST + +//------------------------------------------------------------------------- +// helpers +//------------------------------------------------------------------------- +#define INITIALIZE(obj, bytes_to_extract_value, offset_value, relative_flag_value, \ + string_convert_flag_value, align_value, endianness_value, base_value,\ + multiplier_value, bitmask_val_value, var_number_value, name_value) \ + obj.base = base_value; \ + obj.bitmask_val = bitmask_val_value; \ + obj.bytes_to_extract = bytes_to_extract_value; \ + obj.offset = offset_value; \ + obj.endianness = endianness_value; \ + obj.relative_flag = relative_flag_value; \ + obj.string_convert_flag = string_convert_flag_value; \ + obj.multiplier = multiplier_value; \ + obj.align = align_value; \ + obj.var_number = var_number_value; \ + obj.name = name_value; + +class ByteExtractDataMatcher + : public Catch::Matchers::Impl::MatcherBase +{ +public: + ByteExtractDataMatcher(const ByteExtractData& value) : m_value(value) {} + + bool match(ByteExtractData const& rhs) const override + { + return ((m_value.bytes_to_extract == rhs.bytes_to_extract) and + (m_value.offset == rhs.offset) and + (m_value.relative_flag == rhs.relative_flag) and + (m_value.string_convert_flag == rhs.string_convert_flag) and + (m_value.align == rhs.align) and + (m_value.endianness == rhs.endianness) and + (m_value.base == rhs.base) and + (m_value.multiplier == rhs.multiplier) and + (m_value.var_number == rhs.var_number) and + (m_value.bitmask_val == rhs.bitmask_val)); + } + + std::string describe() const override + { + std::ostringstream ss; + ss << "settings is equals to:\n"; + ss << "bytes_to_extract : " << m_value.bytes_to_extract << ";\n"; + ss << "offset : " << m_value.offset << ";\n"; + ss << "relative_flag : " << m_value.relative_flag << ";\n"; + ss << "string_convert_flag : " << m_value.string_convert_flag << ";\n"; + ss << "align : " << m_value.align << ";\n"; + ss << "endianness : " << m_value.endianness << ";\n"; + ss << "base : " << m_value.base << ";\n"; + ss << "multiplier : " << m_value.multiplier << ";\n"; + ss << "bitmask_val : " << m_value.bitmask_val << ";\n"; + ss << "var_number : " << m_value.var_number << ";\n"; + return ss.str(); + } + +private: + ByteExtractData m_value; +}; + +ByteExtractDataMatcher ByteExtractDataEquals(const ByteExtractData& value) +{ + return {value}; +} + +class SetBufferOptionHelper : public IpsOption +{ +public: + SetBufferOptionHelper(const char* value) + : IpsOption(value, RULE_OPTION_TYPE_BUFFER_SET) + { } +}; + +//------------------------------------------------------------------------- +// option tests +//------------------------------------------------------------------------- + +TEST_CASE("ByteExtractOption::operator== valid", "[ips_byte_extract]") +{ + SetBufferOptionHelper set_buf("test"); + + char* lhs_name = new char[9]; + strcpy(lhs_name, "test_lhs"); + ByteExtractData data_lhs; + INITIALIZE(data_lhs, 0, 0, 0, 0, 0, 0, 8, 1, 0, 0, lhs_name); + ByteExtractOption lhs(data_lhs); + + char* rhs_name = new char[9]; + strcpy(rhs_name, "test_rhs"); + ByteExtractData data_rhs; + INITIALIZE(data_rhs, 0, 0, 0, 0, 0, 0, 8, 1, 0, 0, rhs_name); + ByteExtractOption rhs(data_rhs); + + CHECK(lhs == rhs); +} + +TEST_CASE("ByteExtractOption::operator== invalid", "[ips_byte_extract]") +{ + SetBufferOptionHelper set_buf("test"); + + char* lhs_name = new char[5]; + strcpy(lhs_name, "test"); + ByteExtractData data_lhs; + INITIALIZE(data_lhs, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, nullptr); + ByteExtractOption lhs(data_lhs); + + SECTION("not equal to IpsOption object") + { + CHECK(lhs != set_buf); + delete[] lhs_name; + } + SECTION("all fields is different") + { + ByteExtractData data_rhs; + INITIALIZE(data_rhs, 1, 4, true, false, 2, ENDIAN_FUNC, 0, 1, 0x1, 0, lhs_name); + ByteExtractOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("bytes_to_extract is different") + { + ByteExtractData data_rhs; + INITIALIZE(data_rhs, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, lhs_name); + ByteExtractOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("offset is different") + { + ByteExtractData data_rhs; + INITIALIZE(data_rhs, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, lhs_name); + ByteExtractOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("relative_flag is different") + { + ByteExtractData data_rhs; + INITIALIZE(data_rhs, 0, 0, true, 0, 0, 0, 0, 1, 0, 0, lhs_name); + ByteExtractOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("string_convert_flag is different") + { + ByteExtractData data_rhs; + INITIALIZE(data_rhs, 0, 0, 0, true, 0, 0, 0, 1, 0, 0, lhs_name); + ByteExtractOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("align is different") + { + ByteExtractData data_rhs; + INITIALIZE(data_rhs, 0, 0, 0, 0, 2, 0, 0, 1, 0, 0, lhs_name); + ByteExtractOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("endianness is different") + { + ByteExtractData data_rhs; + INITIALIZE(data_rhs, 0, 0, 0, 0, 0, ENDIAN_FUNC, 0, 1, 0, 0, lhs_name); + ByteExtractOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("base is different") + { + ByteExtractData data_rhs; + INITIALIZE(data_rhs, 0, 0, 0, 0, 0, 0, 16, 1, 0, 0, lhs_name); + ByteExtractOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("multiplier is different") + { + ByteExtractData data_rhs; + INITIALIZE(data_rhs, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, lhs_name); + ByteExtractOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("bitmask is different") + { + ByteExtractData data_rhs; + INITIALIZE(data_rhs, 0, 0, 0, 0, 0, 0, 0, 1, 0xFFFF, 0, lhs_name); + ByteExtractOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("var_number is different") + { + ByteExtractData data_rhs; + INITIALIZE(data_rhs, 0, 0, 0, 0, 0, 0, 0, 1, 0, 3, lhs_name); + ByteExtractOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("name is different") + { + delete[] lhs_name; + char* rhs_name = new char[5]; + strcpy(rhs_name, "unix"); + ByteExtractData data_rhs; + INITIALIZE(data_rhs, 0, 0, 0, 0, 0, 0, 0, 1, 0, 3, rhs_name); + ByteExtractOption rhs(data_rhs); + CHECK(lhs != rhs); + } +} + +TEST_CASE("ByteExtractOption::hash", "[ips_byte_extract]") +{ + SetBufferOptionHelper set_buf("test"); + + ByteExtractData data_lhs; + INITIALIZE(data_lhs, 0, 0, 0, 0, 0, 0, 8, 1, 0, 0, nullptr); + ByteExtractOption lhs(data_lhs); + + SECTION("hash codes of any two equal objects are equal") + { + ByteExtractData data_rhs; + INITIALIZE(data_rhs, 0, 0, 0, 0, 0, 0, 8, 1, 0, 0, nullptr); + ByteExtractOption rhs(data_rhs); + + CHECK(lhs.hash() == rhs.hash()); + } +} + +TEST_CASE("ByteExtractOption::eval valid", "[ips_byte_extract]") +{ + Packet p; + p.data = (const uint8_t*)"Lorem 12345"; + p.dsize = 11; + Cursor c(&p); + + for (unsigned i = 0; i < NUM_IPS_OPTIONS_VARS; ++i) + { + SetVarValueByIndex(0, i); + } + ClearIpsOptionsVars(); + + char* name = new char[5]; + strcpy(name, "test"); + + SECTION("1 byte read, offset 6, string conversion, base 10, align 2") + { + ByteExtractData data; + INITIALIZE(data, 1, 6, 0, 1, 2, ENDIAN_BIG, 10, 1, 0, 0, name); + ByteExtractOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::MATCH); + uint32_t res = 0; + GetVarValueByIndex(&res, 0); + CHECK(res == 2); + CHECK(c.get_pos() == 7); + } + SECTION("3 byte read, offset 6, string conversion, base 10, align 4") + { + ByteExtractData data; + INITIALIZE(data, 3, 6, 0, 1, 4, ENDIAN_BIG, 10, 1, 0, 0, name); + ByteExtractOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::MATCH); + uint32_t res = 0; + GetVarValueByIndex(&res, 0); + CHECK(res == 124); + CHECK(c.get_pos() == 9); + } + SECTION("1 byte read, offset 1, no string conversion, align 2, multiply 3") + { + ByteExtractData data; + INITIALIZE(data, 1, 1, 0, 0, 2, ENDIAN_BIG, 0, 3, 0, 0, name); + ByteExtractOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::MATCH); + uint32_t res = 0; + GetVarValueByIndex(&res, 0); + CHECK(res == 334); + CHECK(c.get_pos() == 2); + } + SECTION("1 byte read, offset 3, no string conversion, align 4, multiply 5") + { + ByteExtractData data; + INITIALIZE(data, 1, 3, 0, 0, 4, ENDIAN_BIG, 0, 5, 0, 0, name); + ByteExtractOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::MATCH); + uint32_t res = 0; + GetVarValueByIndex(&res, 0); + CHECK(res == 508); + CHECK(c.get_pos() == 4); + } +} + +TEST_CASE("ByteExtractOption::eval invalid", "[ips_byte_extract]") +{ + Packet p; + p.data = (const uint8_t*)"Lorem 9876"; + p.dsize = 11; + Cursor c(&p); + + for (unsigned i = 0; i < NUM_IPS_OPTIONS_VARS; ++i) + { + SetVarValueByIndex(0, i); + } + ClearIpsOptionsVars(); + + char* name = new char[5]; + strcpy(name, "test"); + + SECTION("align value to 1") + { + ByteExtractData data; + INITIALIZE(data, 1, 0, 0, 0, 1, ENDIAN_BIG, 0, 1, 0, 0, name); + ByteExtractOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::MATCH); + uint32_t res = 0; + GetVarValueByIndex(&res, 0); + CHECK(res == 76); + CHECK(c.get_pos() == 1); + } + SECTION("align value to 6") + { + ByteExtractData data; + INITIALIZE(data, 1, 0, 0, 0, 6, ENDIAN_BIG, 0, 1, 0, 0, name); + ByteExtractOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::MATCH); + uint32_t res = 0; + GetVarValueByIndex(&res, 0); + CHECK(res == 76); + CHECK(c.get_pos() == 1); + } +} + +//------------------------------------------------------------------------- +// module tests +//------------------------------------------------------------------------- + +TEST_CASE("ExtractModule lifecycle", "[ips_byte_extract]") +{ + ExtractModule obj; + + SECTION("test of constructor") + { + CHECK(obj.data.multiplier == 1); + } + SECTION("test of \"begin\" method") + { + CHECK(obj.begin(nullptr, 0, nullptr)); + ByteExtractData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, nullptr); + + CHECK_THAT(obj.data, ByteExtractDataEquals(expected)); + } + SECTION("test of \"end\" method") + { + obj.begin(nullptr, 0, nullptr); + + Value v_name("test"); + Parameter p_name{ + "~name", Parameter::PT_STRING, nullptr, nullptr, + "name of the variable that will be used in other rule options"}; + v_name.set(&p_name); + obj.set(nullptr, v_name, nullptr); + + Value v_bytes(4.0); + Parameter p_bytes{ + "~count", Parameter::PT_INT, "1:10", nullptr, + "number of bytes to pick up from the buffer"}; + v_bytes.set(&p_bytes); + obj.set(nullptr, v_bytes, nullptr); + + CHECK(obj.end(nullptr, 0, nullptr)); + + char* name = new char[5]; + strcpy(name, "test"); + ByteExtractData expected; + INITIALIZE(expected, 4, 0, 0, 0, 0, ENDIAN_BIG, 0, 1, 0, 0, name); + + CHECK_THAT(obj.data, ByteExtractDataEquals(expected)); + + delete[] name; + delete[] obj.data.name; + } +} + +TEST_CASE("Test of byte_extract_ctor", "[ips_byte_extract]") +{ + ClearIpsOptionsVars(); + + std::string name = "test"; + for (unsigned i = 0; i <= NUM_IPS_OPTIONS_VARS; ++i) + { + ExtractModule obj; + obj.begin(nullptr, 0, nullptr); + Value v((name + std::to_string(i)).c_str()); + Parameter p{ + "~name", Parameter::PT_STRING, nullptr, nullptr, + "name of the variable that will be used in other rule options"}; + v.set(&p); + obj.set(nullptr, v, nullptr); + + if (i < NUM_IPS_OPTIONS_VARS) + { + IpsOption* res = byte_extract_ctor(&obj, nullptr); + delete res; + } + else + { + IpsOption* res_null = byte_extract_ctor(&obj, nullptr); + CHECK(res_null == nullptr); + delete[] obj.data.name; + } + } +} + +TEST_CASE("ExtractModule::set", "[ips_byte_extract]") +{ + ExtractModule obj; + obj.begin(nullptr, 0, nullptr); + + SECTION("set bytes_to_extract") + { + Value v(4.0); + Parameter p{ + "~count", Parameter::PT_INT, "1:10", nullptr, + "number of bytes to pick up from the buffer"}; + v.set(&p); + ByteExtractData expected; + INITIALIZE(expected, 4, 0, 0, 0, 0, 0, 0, 1, 0, 0, nullptr); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteExtractDataEquals(expected)); + } + SECTION("set offset") + { + Value v(7.0); + Parameter p{ + "~offset", Parameter::PT_INT, "-65535:65535", nullptr, + "number of bytes into the buffer to start processing"}; + v.set(&p); + ByteExtractData expected; + INITIALIZE(expected, 0, 7, 0, 0, 0, 0, 0, 1, 0, 0, nullptr); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteExtractDataEquals(expected)); + } + SECTION("set name") + { + Value v("test_name"); + Parameter p{ + "~name", Parameter::PT_STRING, nullptr, nullptr, + "name of the variable that will be used in other rule options"}; + v.set(&p); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data.name, Catch::Matchers::Equals("test_name")); + } + SECTION("set relative") + { + Value v(true); + Parameter p{ + "relative", Parameter::PT_IMPLIED, nullptr, nullptr, + "offset from cursor instead of start of buffer"}; + v.set(&p); + obj.set(nullptr, v, nullptr); + ByteExtractData expected; + INITIALIZE(expected, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, nullptr); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteExtractDataEquals(expected)); + } + SECTION("set multiplier") + { + Value v(6.0); + Parameter p{ + "multiplier", Parameter::PT_INT, "1:65535", "1", + "scale extracted value by given amount"}; + v.set(&p); + ByteExtractData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, nullptr); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteExtractDataEquals(expected)); + } + SECTION("set align") + { + Value v(2.0); + Parameter p{ + "align", Parameter::PT_INT, "0:4", "0", + "round the number of converted bytes up to the next 2- " + "or 4-byte boundary"}; + v.set(&p); + ByteExtractData expected; + INITIALIZE(expected, 0, 0, 0, 0, 2, 0, 0, 1, 0, 0, nullptr); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteExtractDataEquals(expected)); + } + SECTION("set endianness") + { + Value v_big((double)ENDIAN_BIG); + Parameter p_big{"big", Parameter::PT_IMPLIED, nullptr, nullptr, "big endian"}; + v_big.set(&p_big); + obj.set(nullptr, v_big, nullptr); + ByteExtractData expected_big; + INITIALIZE(expected_big, 0, 0, 0, 0, 0, ENDIAN_BIG, 0, 1, 0, 0, nullptr); + + CHECK(obj.set(nullptr, v_big, nullptr)); + CHECK_THAT(obj.data, ByteExtractDataEquals(expected_big)); + + Value v_lit((double)ENDIAN_LITTLE); + Parameter p_lit{"little", Parameter::PT_IMPLIED, nullptr, nullptr, "little endian"}; + v_lit.set(&p_lit); + ByteExtractData expected_lit; + INITIALIZE(expected_lit, 0, 0, 0, 0, 0, ENDIAN_LITTLE, 0, 1, 0, 0, nullptr); + + CHECK(obj.set(nullptr, v_lit, nullptr)); + CHECK_THAT(obj.data, ByteExtractDataEquals(expected_lit)); + + Value v_dce((double)ENDIAN_FUNC); + Parameter p_dce{"dce", Parameter::PT_IMPLIED, nullptr, nullptr, + "dcerpc2 determines endianness"}; + v_dce.set(&p_dce); + ByteExtractData expected_dce; + INITIALIZE(expected_dce, 0, 0, 0, 0, 0, ENDIAN_FUNC, 0, 1, 0, 0, nullptr); + + CHECK(obj.set(nullptr, v_dce, nullptr)); + CHECK_THAT(obj.data, ByteExtractDataEquals(expected_dce)); + } + SECTION("set string") + { + Value v(true); + Parameter p{ + "string", Parameter::PT_IMPLIED, nullptr, nullptr, + "convert from string"}; + v.set(&p); + ByteExtractData expected; + INITIALIZE(expected, 0, 0, 0, 1, 0, 0, 10, 1, 0, 0, nullptr); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteExtractDataEquals(expected)); + } + SECTION("set hex") + { + Value v(true); + Parameter p{ + "hex", Parameter::PT_IMPLIED, nullptr, nullptr, + "convert from hex string"}; + v.set(&p); + ByteExtractData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, 0, 16, 1, 0, 0, nullptr); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteExtractDataEquals(expected)); + } + SECTION("set oct") + { + Value v(true); + Parameter p{ + "oct", Parameter::PT_IMPLIED, nullptr, nullptr, + "convert from octal string"}; + v.set(&p); + ByteExtractData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, 0, 8, 1, 0, 0, nullptr); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteExtractDataEquals(expected)); + } + SECTION("set dec") + { + Value v(true); + Parameter p{ + "dec", Parameter::PT_IMPLIED, nullptr, nullptr, + "convert from decimal string"}; + v.set(&p); + ByteExtractData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, 0, 10, 1, 0, 0, nullptr); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteExtractDataEquals(expected)); + } + SECTION("set bitmask") + { + Value v(1023.0); + Parameter p{ + "bitmask", Parameter::PT_INT, "0x1:0xFFFFFFFF", nullptr, + "applies as an AND to the extracted value before " + "storage in 'name'"}; + v.set(&p); + ByteExtractData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, 0, 0, 1, 1023, 0, nullptr); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteExtractDataEquals(expected)); + } + SECTION("invalid set") + { + Value v(1023.0); + Parameter p{ + "error", Parameter::PT_INT, "nan", nullptr, + "not an option"}; + v.set(&p); + ByteExtractData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, nullptr); + + CHECK(!obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteExtractDataEquals(expected)); + } + + delete[] obj.data.name; +} + +//------------------------------------------------------------------------- +// api tests +//------------------------------------------------------------------------- + +TEST_CASE("ByteExtractVerify_valid", "[ips_byte_extract]") +{ + ByteExtractData obj; + char name[] = "test"; + + SECTION("Minimum values, no string conversion") + { + INITIALIZE(obj, 1, -65535, 1, 0, 0, ENDIAN_FUNC, 0, 1, 0x1, 0, name); + CHECK(ByteExtractVerify(&obj)); + } + SECTION("Maximum values, no string conversion") + { + INITIALIZE(obj, MAX_BYTES_TO_GRAB, 65535, 1, 0, 4, ENDIAN_FUNC, + 0, 65535, 0xFFFFFFFF, 0, name); + CHECK(ByteExtractVerify(&obj)); + } + SECTION("Minimum values, with string conversion") + { + INITIALIZE(obj, 1, -65535, 1, 1, 0, ENDIAN_FUNC, 8, 1, 0x1, 0, name); + CHECK(ByteExtractVerify(&obj)); + } + SECTION("Maximum values, with string conversion") + { + INITIALIZE(obj, PARSELEN, 65535, 1, 1, 4, ENDIAN_FUNC, 16, 65535, 0xFFFFFFFF, 0, name); + CHECK(ByteExtractVerify(&obj)); + } +} + +TEST_CASE("ByteExtractVerify_invalid", "[ips_byte_extract]") +{ + char* name = new char[5]; + strcpy(name, "test"); + ByteExtractData obj; + INITIALIZE(obj, 2, 7, 0, 0, 2, ENDIAN_FUNC, 0, 1, 0, 0, name); + + SECTION("bytes_to_extract checks") + { + obj.bytes_to_extract = MAX_BYTES_TO_GRAB + 1; + CHECK((!ByteExtractVerify(&obj))); + + obj.string_convert_flag = true; + obj.bytes_to_extract = PARSELEN + 1; + CHECK((!ByteExtractVerify(&obj))); + } + SECTION("align checks") + { + obj.align = 1; + CHECK((!ByteExtractVerify(&obj))); + + obj.align = 6; + CHECK((!ByteExtractVerify(&obj))); + } + SECTION("offset checks") + { + obj.offset = -5; + CHECK((!ByteExtractVerify(&obj))); + } + SECTION("name checks") + { + delete[] name; + obj.name = nullptr; + CHECK((!ByteExtractVerify(&obj))); + + name = new char[6]; + strcpy(name, "64bit"); + obj.name = name; + CHECK((!ByteExtractVerify(&obj))); + } + SECTION("base checks") + { + obj.base = 16; + CHECK((!ByteExtractVerify(&obj))); + } + SECTION("bitmask checks") + { + obj.bytes_to_extract = 2; + obj.bitmask_val = 1048575; + CHECK((!ByteExtractVerify(&obj))); + } + delete[] name; +} + +#endif diff --git a/src/ips_options/ips_byte_jump.cc b/src/ips_options/ips_byte_jump.cc index 003ae7900..8c70ce59c 100644 --- a/src/ips_options/ips_byte_jump.cc +++ b/src/ips_options/ips_byte_jump.cc @@ -28,7 +28,7 @@ * * Arguments: * Required: - * : number of bytes to pick up from the packet + * : number of bytes to pick up from the packet * : number of bytes into the payload to grab the bytes * Optional: * ["relative"]: offset relative to last pattern match @@ -95,30 +95,23 @@ static THREAD_LOCAL ProfileStats byteJumpPerfStats; #define s_name "byte_jump" -typedef struct _ByteJumpData +struct ByteJumpData : public ByteData { - uint32_t bytes_to_grab; - int32_t offset; - uint8_t relative_flag; - uint8_t data_string_convert_flag; - uint8_t from_beginning_flag; - uint8_t align_flag; - uint8_t endianness; - uint32_t base; uint32_t multiplier; int32_t post_offset; - uint32_t bitmask_val; int8_t offset_var; - uint8_t from_end_flag; int8_t post_offset_var; -} ByteJumpData; + bool align_flag; + bool from_beginning_flag; + bool from_end_flag; +}; class ByteJumpOption : public IpsOption { public: - ByteJumpOption(const ByteJumpData& c) : IpsOption(s_name, RULE_OPTION_TYPE_BUFFER_USE) - { config = c; } - + ByteJumpOption(const ByteJumpData& c) : IpsOption(s_name, + RULE_OPTION_TYPE_BUFFER_USE), config(c) + { } uint32_t hash() const override; bool operator==(const IpsOption&) const override; @@ -141,14 +134,14 @@ private: uint32_t ByteJumpOption::hash() const { - uint32_t a = config.bytes_to_grab; + uint32_t a = config.bytes_to_extract; uint32_t b = config.offset; uint32_t c = config.base; mix(a,b,c); a += (config.relative_flag << 24 | - config.data_string_convert_flag << 16 | + config.string_convert_flag << 16 | config.from_beginning_flag << 8 | config.align_flag); b += config.endianness; @@ -157,7 +150,9 @@ uint32_t ByteJumpOption::hash() const mix(a,b,c); a += config.post_offset; - b += config.from_end_flag << 16 | (uint32_t) config.offset_var << 8 | config.post_offset_var; + b += config.from_end_flag << 16 | + (uint32_t) config.offset_var << 8 | + config.post_offset_var; c += config.bitmask_val; mix(a,b,c); @@ -169,26 +164,26 @@ uint32_t ByteJumpOption::hash() const bool ByteJumpOption::operator==(const IpsOption& ips) const { - if ( !IpsOption::operator==(ips) ) + if (!IpsOption::operator==(ips)) return false; const ByteJumpOption& rhs = (const ByteJumpOption&)ips; const ByteJumpData* left = &config; const ByteJumpData* right = &rhs.config; - if (( left->bytes_to_grab == right->bytes_to_grab) && - ( left->offset == right->offset) && - ( left->offset_var == right->offset_var) && - ( left->relative_flag == right->relative_flag) && - ( left->data_string_convert_flag == right->data_string_convert_flag) && - ( left->from_beginning_flag == right->from_beginning_flag) && - ( left->align_flag == right->align_flag) && - ( left->endianness == right->endianness) && - ( left->base == right->base) && - ( left->multiplier == right->multiplier) && - ( left->post_offset == right->post_offset) && - ( left->bitmask_val == right->bitmask_val) && - ( left->from_end_flag == right->from_end_flag) && + if (( left->bytes_to_extract == right->bytes_to_extract) and + ( left->offset == right->offset) and + ( left->offset_var == right->offset_var) and + ( left->relative_flag == right->relative_flag) and + ( left->string_convert_flag == right->string_convert_flag) and + ( left->from_beginning_flag == right->from_beginning_flag) and + ( left->align_flag == right->align_flag) and + ( left->endianness == right->endianness) and + ( left->base == right->base) and + ( left->multiplier == right->multiplier) and + ( left->post_offset == right->post_offset) and + ( left->bitmask_val == right->bitmask_val) and + ( left->from_end_flag == right->from_end_flag) and ( left->post_offset_var == right->post_offset_var)) { return true; @@ -207,7 +202,7 @@ IpsOption::EvalStatus ByteJumpOption::eval(Cursor& c, Packet* p) int32_t post_offset = 0; // Get values from byte_extract variables, if present. - if (bjd->offset_var >= 0 && bjd->offset_var < NUM_IPS_OPTIONS_VARS) + if (bjd->offset_var >= 0 and bjd->offset_var < NUM_IPS_OPTIONS_VARS) { uint32_t extract_offset; GetVarValueByIndex(&extract_offset, bjd->offset_var); @@ -217,7 +212,8 @@ IpsOption::EvalStatus ByteJumpOption::eval(Cursor& c, Packet* p) { offset = bjd->offset; } - if (bjd->post_offset_var >= 0 && bjd->post_offset_var < NUM_IPS_OPTIONS_VARS) + if (bjd->post_offset_var >= 0 and + bjd->post_offset_var < NUM_IPS_OPTIONS_VARS) { uint32_t extract_post_offset; GetVarValueByIndex(&extract_post_offset, bjd->post_offset_var); @@ -227,60 +223,22 @@ IpsOption::EvalStatus ByteJumpOption::eval(Cursor& c, Packet* p) { post_offset = bjd->post_offset; } - - const uint8_t* const start_ptr = c.buffer(); - const uint8_t* const end_ptr = start_ptr + c.size(); - const uint8_t* const base_ptr = offset + - ((bjd->relative_flag) ? c.start() : start_ptr); + ByteJumpData extract_config = config; + extract_config.offset = offset; + const uint8_t *start_ptr, *end_ptr, *base_ptr; + set_cursor_bounds(extract_config, c, start_ptr, base_ptr, end_ptr); uint32_t jump = 0; - uint32_t payload_bytes_grabbed = 0; - uint8_t endian = bjd->endianness; - - if (endian == ENDIAN_FUNC) - { - if (!p->endianness || - !p->endianness->get_offset_endianness(base_ptr - p->data, endian)) - return NO_MATCH; - } + int32_t payload_bytes_grabbed = 0; - // Both of the extraction functions contain checks to ensure the data - // is inbounds and will return no match if it isn't - if (bjd->bytes_to_grab) + if (bjd->bytes_to_extract) { - if ( !bjd->data_string_convert_flag ) - { - if ( byte_extract( - endian, bjd->bytes_to_grab, - base_ptr, start_ptr, end_ptr, &jump) ) - return NO_MATCH; - - payload_bytes_grabbed = bjd->bytes_to_grab; - } - else - { - int32_t tmp = string_extract( - bjd->bytes_to_grab, bjd->base, - base_ptr, start_ptr, end_ptr, &jump); - - if (tmp < 0) - return NO_MATCH; - - payload_bytes_grabbed = tmp; - } - - // Negative offsets that put us outside the buffer should have been caught - // in the extraction routines - assert(base_ptr >= c.buffer()); + payload_bytes_grabbed = data_extraction(extract_config, p, + jump, start_ptr, base_ptr, end_ptr); - if (bjd->bitmask_val != 0 ) + if (NO_MATCH == payload_bytes_grabbed) { - uint32_t num_tailing_zeros_bitmask = getNumberTailingZerosInBitmask(bjd->bitmask_val); - jump = jump & bjd->bitmask_val; - if (jump && num_tailing_zeros_bitmask ) - { - jump = jump >> num_tailing_zeros_bitmask; - } + return NO_MATCH; } if (bjd->multiplier) @@ -296,12 +254,12 @@ IpsOption::EvalStatus ByteJumpOption::eval(Cursor& c, Packet* p) } } - uint32_t pos; + uint32_t pos = 0; - if ( bjd->from_beginning_flag ) + if (bjd->from_beginning_flag) pos = 0; - else if ( bjd->from_end_flag ) + else if (bjd->from_end_flag) pos = c.size(); else @@ -309,7 +267,7 @@ IpsOption::EvalStatus ByteJumpOption::eval(Cursor& c, Packet* p) pos += jump + post_offset; - if ( !c.set_pos(pos) ) + if (!c.set_pos(pos)) return NO_MATCH; return MATCH; @@ -343,7 +301,8 @@ static const Parameter s_params[] = "round the number of converted bytes up to the next 2- or 4-byte boundary" }, { "post_offset", Parameter::PT_STRING, nullptr, nullptr, - "skip forward or backward (positive or negative value) by variable name or number of " \ + "skip forward or backward (positive or negative value) " \ + "by variable name or number of " \ "bytes after the other jump options have been applied" }, { "big", Parameter::PT_IMPLIED, nullptr, nullptr, @@ -379,7 +338,8 @@ static const Parameter s_params[] = class ByteJumpModule : public Module { public: - ByteJumpModule() : Module(s_name, s_help, s_params) { data.multiplier = 1; } + ByteJumpModule() : Module(s_name, s_help, s_params) + { data.multiplier = 1; } bool begin(const char*, int, SnortConfig*) override; bool end(const char*, int, SnortConfig*) override; @@ -408,7 +368,7 @@ bool ByteJumpModule::begin(const char*, int, SnortConfig*) bool ByteJumpModule::end(const char*, int, SnortConfig*) { - if ( var.empty() ) + if (var.empty()) data.offset_var = IPS_OPTIONS_NO_VAR; else { @@ -420,7 +380,7 @@ bool ByteJumpModule::end(const char*, int, SnortConfig*) return false; } } - if ( post_var.empty() ) + if (post_var.empty()) data.post_offset_var = IPS_OPTIONS_NO_VAR; else { @@ -432,26 +392,29 @@ bool ByteJumpModule::end(const char*, int, SnortConfig*) return false; } } - if ( !data.endianness ) + if (!data.endianness) data.endianness = ENDIAN_BIG; - if (data.from_beginning_flag && data.from_end_flag) + if (data.from_beginning_flag and data.from_end_flag) { ParseError("from_beginning and from_end options together in a rule\n"); return false; } - if ( data.bitmask_val && (numBytesInBitmask(data.bitmask_val) > data.bytes_to_grab)) + if (data.bitmask_val and + (numBytesInBitmask(data.bitmask_val) > data.bytes_to_extract)) { - ParseError("Number of bytes in \"bitmask\" value is greater than bytes to extract."); + ParseError("Number of bytes in \"bitmask\" value " \ + "is greater than bytes to extract."); return false; } - if ((data.bytes_to_grab > MAX_BYTES_TO_GRAB) && !data.data_string_convert_flag) + if ((data.bytes_to_extract > MAX_BYTES_TO_GRAB) and + !data.string_convert_flag) { ParseError( - "byte_jump rule option cannot extract more than %d bytes without valid string prefix.", - MAX_BYTES_TO_GRAB); + "byte_jump rule option cannot extract more than %d bytes without "\ + "valid string prefix.", MAX_BYTES_TO_GRAB); return false; } @@ -460,64 +423,64 @@ bool ByteJumpModule::end(const char*, int, SnortConfig*) bool ByteJumpModule::set(const char*, Value& v, SnortConfig*) { - if ( v.is("~count") ) - data.bytes_to_grab = v.get_uint8(); + if (v.is("~count")) + data.bytes_to_extract = v.get_uint8(); - else if ( v.is("~offset") ) + else if (v.is("~offset")) { long n; - if ( v.strtol(n) ) + if (v.strtol(n)) data.offset = n; else var = v.get_string(); } - else if ( v.is("relative") ) + else if (v.is("relative")) data.relative_flag = 1; - else if ( v.is("from_beginning") ) + else if (v.is("from_beginning")) data.from_beginning_flag = 1; - else if ( v.is("align") ) + else if (v.is("align")) data.align_flag = 1; - else if ( v.is("multiplier") ) + else if (v.is("multiplier")) data.multiplier = v.get_uint16(); - else if ( v.is("post_offset") ) + else if (v.is("post_offset")) { long n; - if ( v.strtol(n) ) + if (v.strtol(n)) data.post_offset = n; else post_var = v.get_string(); } - else if ( v.is("big") ) + else if (v.is("big")) set_byte_order(data.endianness, ENDIAN_BIG, "byte_jump"); - else if ( v.is("little") ) + else if (v.is("little")) set_byte_order(data.endianness, ENDIAN_LITTLE, "byte_jump"); - else if ( v.is("dce") ) + else if (v.is("dce")) set_byte_order(data.endianness, ENDIAN_FUNC, "byte_jump"); - else if ( v.is("string") ) + else if (v.is("string")) { - data.data_string_convert_flag = 1; + data.string_convert_flag = 1; data.base = 10; } - else if ( v.is("dec") ) + else if (v.is("dec")) data.base = 10; - else if ( v.is("hex") ) + else if (v.is("hex")) data.base = 16; - else if ( v.is("oct") ) + else if (v.is("oct")) data.base = 8; - else if ( v.is("from_end") ) + else if (v.is("from_end")) data.from_end_flag = 1; - else if ( v.is("bitmask") ) + else if (v.is("bitmask")) data.bitmask_val = v.get_uint32(); else @@ -586,3 +549,513 @@ const BaseApi* ips_byte_jump[] = nullptr }; +//------------------------------------------------------------------------- +// UNIT TESTS +//------------------------------------------------------------------------- +#ifdef UNIT_TEST +#include +#include + +#include "framework/value.h" +#include "framework/parameter.h" + +#include "catch/snort_catch.h" + +#define NO_MATCH snort::IpsOption::EvalStatus::NO_MATCH +#define MATCH snort::IpsOption::EvalStatus::MATCH + +void SetByteJumpData(ByteJumpData &byte_jump, int value) +{ + byte_jump.bytes_to_extract = value; + byte_jump.offset = value; + byte_jump.relative_flag = value; + byte_jump.string_convert_flag = value; + byte_jump.from_beginning_flag = value; + byte_jump.align_flag = value; + byte_jump.endianness = value; + byte_jump.base = value; + byte_jump.multiplier = value; + byte_jump.post_offset = value; + byte_jump.bitmask_val = value; + byte_jump.offset_var = value; + byte_jump.from_end_flag = value; + byte_jump.post_offset_var = value; +} + +void SetByteJumpMaxValue(ByteJumpData &byte_jump) +{ + byte_jump.bytes_to_extract = UINT_MAX; + byte_jump.offset = INT_MAX; + byte_jump.relative_flag = UCHAR_MAX; + byte_jump.string_convert_flag = UCHAR_MAX; + byte_jump.from_beginning_flag = UCHAR_MAX; + byte_jump.align_flag = UCHAR_MAX; + byte_jump.endianness = UCHAR_MAX; + byte_jump.base = UINT_MAX; + byte_jump.multiplier = UINT_MAX; + byte_jump.post_offset = INT_MAX; + byte_jump.bitmask_val = UINT_MAX; + byte_jump.offset_var = SCHAR_MAX; + byte_jump.from_end_flag = UCHAR_MAX; + byte_jump.post_offset_var = SCHAR_MAX; +} + +class StubIpsOption : public IpsOption +{ +public: + StubIpsOption(const char* name, option_type_t option_type) : + IpsOption(name, option_type) + { } +}; + +class StubEndianness : public Endianness +{ +public: + StubEndianness() = default; + virtual bool get_offset_endianness(int32_t, uint8_t&) override + { return false; } +}; + +TEST_CASE("ByteJumpOption test", "[ips_byte_jump]") +{ + ByteJumpData byte_jump; + SetByteJumpData(byte_jump, 1); + snort::IpsOption::set_buffer("hello_world"); + + SECTION("method hash") + { + ByteJumpOption hash_test(byte_jump); + ByteJumpOption hash_test_equal(byte_jump); + + SECTION("Testing hash with very low values") + { + SECTION("Hash has same source") + { + REQUIRE(hash_test.hash() == hash_test_equal.hash()); + } + + SECTION("Compare hash from different source") + { + SetByteJumpData(byte_jump, 4); + ByteJumpOption hash_test_diff(byte_jump); + CHECK(hash_test.hash() != hash_test_diff.hash()); + } + } + + SECTION("Testing hash with maximum values") + { + SetByteJumpMaxValue(byte_jump); + ByteJumpOption hash_test_max(byte_jump); + ByteJumpOption hash_test_equal_max(byte_jump); + + SECTION("Hash has same source") + { + CHECK(hash_test_max.hash() == hash_test_equal_max.hash()); + } + + SECTION("Compare hash from different source") + { + SetByteJumpMaxValue(byte_jump); + ByteJumpOption hash_test_max(byte_jump); + CHECK(hash_test.hash() != hash_test_max.hash()); + } + } + } + + SECTION("operator ==") + { + ByteJumpOption jump(byte_jump); + + SECTION("Compare IpsOptions with different names") + { + StubIpsOption case_diff_name("not_hello_world", + option_type_t::RULE_OPTION_TYPE_BUFFER_USE); + REQUIRE(jump != case_diff_name); + } + + ByteJumpData byte_jump2; + SetByteJumpData(byte_jump2, 1); + + SECTION("Compare between equals objects") + { + ByteJumpOption jump_1(byte_jump); + REQUIRE(jump == jump_1); + } + + SECTION("bytes_to_extract is different") + { + byte_jump2.bytes_to_extract = 2; + ByteJumpOption jump_2_1(byte_jump2); + REQUIRE(jump != jump_2_1); + } + + SECTION("offset is different") + { + byte_jump2.offset = 2; + ByteJumpOption jump_2_2(byte_jump2); + REQUIRE(jump != jump_2_2); + } + + SECTION("relative_flag is different") + { + byte_jump2.relative_flag = 0; + ByteJumpOption jump_2_3(byte_jump2); + REQUIRE(jump != jump_2_3); + } + + SECTION("string_convert_flag is different") + { + byte_jump2.string_convert_flag = 0; + ByteJumpOption jump_2_4(byte_jump2); + REQUIRE(jump != jump_2_4); + } + + SECTION("from_beginning_flag is different") + { + byte_jump2.from_beginning_flag = 0; + ByteJumpOption jump_2_5(byte_jump2); + REQUIRE(jump != jump_2_5); + } + + SECTION("align_flag is different") + { + byte_jump2.align_flag = 0; + ByteJumpOption jump_2_6(byte_jump2); + REQUIRE(jump != jump_2_6); + } + + SECTION("endianness is different") + { + byte_jump2.endianness = 0; + ByteJumpOption jump_2_7(byte_jump2); + REQUIRE(jump != jump_2_7); + } + + SECTION("base is different") + { + byte_jump2.base = 2; + ByteJumpOption jump_2_8(byte_jump2); + REQUIRE(jump != jump_2_8); + } + + SECTION("multiplier is different") + { + byte_jump2.multiplier = 2; + ByteJumpOption jump_2_9(byte_jump2); + REQUIRE(jump != jump_2_9); + } + + SECTION("post_offset is different") + { + byte_jump2.post_offset = 2; + ByteJumpOption jump_2_10(byte_jump2); + REQUIRE(jump != jump_2_10); + } + + SECTION("bitmask_val is different") + { + byte_jump2.bitmask_val = 2; + ByteJumpOption jump_2_11(byte_jump2); + REQUIRE(jump != jump_2_11); + } + + SECTION("offset_var is different") + { + byte_jump2.offset_var = 0; + ByteJumpOption jump_2_12(byte_jump2); + REQUIRE(jump != jump_2_12); + } + + SECTION("from_end_flag is different") + { + byte_jump2.from_end_flag = 0; + ByteJumpOption jump_2_13(byte_jump2); + REQUIRE(jump != jump_2_13); + } + + SECTION("post_offset_var is different") + { + byte_jump2.post_offset_var = 0; + ByteJumpOption jump_2_14(byte_jump2); + REQUIRE(jump != jump_2_14); + } + } + + SECTION("method eval") + { + Packet test_packet; + Cursor current_cursor; + SetByteJumpData(byte_jump, 1); + + SECTION("Cursor not set correct for string_extract") + { + ByteJumpOption test_2(byte_jump); + REQUIRE((test_2.eval(current_cursor, &test_packet)) == NO_MATCH); + } + + SECTION("Extract too much (1000000) bytes from in byte_extract") + { + uint8_t buff = 0; + byte_jump.string_convert_flag = 0; + byte_jump.bytes_to_extract = 1000000; + current_cursor.set("hello_world_long_name", &buff, 50); + ByteJumpOption test_3(byte_jump); + REQUIRE((test_3.eval(current_cursor, &test_packet)) == NO_MATCH); + } + + SECTION("Cursor not set correct") + { + uint8_t buff = 0; + current_cursor.set("hello_world_long_name", &buff, 1); + byte_jump.string_convert_flag = 0; + byte_jump.from_beginning_flag = 0; + byte_jump.from_end_flag = 1; + byte_jump.bytes_to_extract = 1; + byte_jump.post_offset_var = 12; + ByteJumpOption test_4(byte_jump); + REQUIRE((test_4.eval(current_cursor, &test_packet)) == NO_MATCH); + } + + SECTION("Match") + { + uint8_t buff = 0; + byte_jump.string_convert_flag = 0; + byte_jump.from_beginning_flag = 0; + byte_jump.from_end_flag = 0; + current_cursor.set("hello_world_long_name", &buff, 50); + ByteJumpOption test_5(byte_jump); + REQUIRE((test_5.eval(current_cursor, &test_packet)) == MATCH); + + current_cursor.set("hello_world_long_name", (const uint8_t*)"hello", 5); + byte_jump.from_beginning_flag = 1; + byte_jump.bitmask_val = 2; + ByteJumpOption test_5_1(byte_jump); + REQUIRE((test_5_1.eval(current_cursor, &test_packet)) == MATCH); + } + } +} + +TEST_CASE("ByteJumpModule test", "[ips_byte_jump]") +{ + ByteJumpModule module_jump; + ByteJumpData byte_jump; + SetByteJumpData(byte_jump, 1); + + SECTION("method end") + { + std::string buff = "tmp"; + + SECTION("Undefined rule option for var") + { + module_jump.var = buff; + byte_jump.offset_var = -1; + module_jump.data = byte_jump; + REQUIRE(module_jump.end("tmp", 0, nullptr) == false); + } + + SECTION("Undefined rule option for offset_var") + { + module_jump.var.clear(); + module_jump.post_var = buff; + byte_jump.post_offset_var = -1; + module_jump.data = byte_jump; + REQUIRE(module_jump.end("tmp", 0, nullptr) == false); + } + + SECTION("From_beginning and from_end options together") + { + byte_jump.endianness = 0; + module_jump.data = byte_jump; + REQUIRE(module_jump.end("tmp", 0, nullptr) == false); + } + + SECTION("Number of bytes in \"bitmask\" value is greater than bytes to extract") + { + byte_jump.from_beginning_flag = 0; + byte_jump.bytes_to_extract = 0; + module_jump.data = byte_jump; + REQUIRE(module_jump.end("tmp", 0, nullptr) == false); + } + + SECTION("byte_jump rule option cannot extract more \ + than %d bytes without valid string prefix") + { + byte_jump.from_beginning_flag = 0; + byte_jump.bytes_to_extract = 5; + byte_jump.string_convert_flag = 0; + module_jump.data = byte_jump; + REQUIRE(module_jump.end("tmp", 0, nullptr) == false); + } + + SECTION("Case with returned value true") + { + byte_jump.from_beginning_flag = 0; + module_jump.data = byte_jump; + REQUIRE(module_jump.end("tmp", 0, nullptr) == true); + } + } + + SECTION("method set") + { + Value value(false); + + SECTION("All params incorrect") + { + REQUIRE(module_jump.set(nullptr, value, nullptr) == false); + } + + SECTION("Case param \"~count\"") + { + Parameter param("~count", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_jump.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"~offset\"") + { + SECTION("Value doesn't have a str") + { + Parameter param("~offset", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_jump.set(nullptr, value, nullptr) == true); + } + + SECTION("When value has a str") + { + Value value_tmp("123"); + Parameter param("~offset", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value_tmp.set(¶m); + REQUIRE(module_jump.set(nullptr, value_tmp, nullptr) == true); + } + } + + SECTION("Case param \"relative\"") + { + Parameter param("relative", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_jump.set(nullptr, value, nullptr) == true); + } + + SECTION("Param \"from_beginning\" correct") + { + Parameter param("from_beginning", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_jump.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"from_end\"") + { + Parameter param("from_end", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_jump.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"align\"") + { + Parameter param("align", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_jump.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"multiplier\"") + { + Parameter param("multiplier", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_jump.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"post_offset\"") + { + SECTION("Value doesn't have a str") + { + Parameter param("post_offset", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_jump.set(nullptr, value, nullptr) == true); + } + + SECTION("When value has a str") + { + Value value_tmp("123"); + Parameter param("post_offset", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value_tmp.set(¶m); + REQUIRE(module_jump.set(nullptr, value_tmp, nullptr) == true); + } + } + + SECTION("Case param \"big\"") + { + Parameter param("big", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_jump.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"little\"") + { + Parameter param("little", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_jump.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"dce\"") + { + Parameter param("dce", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_jump.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"string\"") + { + Parameter param("string", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_jump.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"dec\"") + { + Parameter param("dec", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_jump.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"hex\"") + { + Parameter param("hex", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_jump.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"oct\"") + { + Parameter param("oct", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_jump.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"bitmask\"") + { + Parameter param("bitmask", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_jump.set(nullptr, value, nullptr) == true); + } + } +} + +#endif diff --git a/src/ips_options/ips_byte_math.cc b/src/ips_options/ips_byte_math.cc index ffa54bbb6..a50cb0003 100644 --- a/src/ips_options/ips_byte_math.cc +++ b/src/ips_options/ips_byte_math.cc @@ -31,11 +31,15 @@ #include "log/messages.h" #include "profiler/profiler.h" #include "protocols/packet.h" -#include "profiler/profiler.h" #include "utils/util.h" #include "extract.h" +#ifdef UNIT_TEST +#include "catch/snort_catch.h" +#include "service_inspectors/dce_rpc/dce_common.h" +#endif + using namespace snort; using namespace std; @@ -57,28 +61,22 @@ enum BM_Oper // must match the exact order in Parameter table - i.e "+|-|*|/|<<| static THREAD_LOCAL ProfileStats byteMathPerfStats; -struct ByteMathData +struct ByteMathData : public ByteData { - uint32_t bytes_to_extract; uint32_t rvalue; - int32_t offset; - uint32_t bitmask_val; - char* result_name; BM_Oper oper; - bool relative_flag; - bool string_convert_flag; - uint8_t base; - uint8_t endianess; + int8_t offset_var; int8_t result_var; int8_t rvalue_var; - int8_t offset_var; + char* result_name; }; class ByteMathOption : public IpsOption { public: - ByteMathOption(const ByteMathData& c) : IpsOption(s_name, RULE_OPTION_TYPE_BUFFER_USE), - config(c) { } + ByteMathOption(const ByteMathData& c) : + IpsOption(s_name, RULE_OPTION_TYPE_BUFFER_USE), config(c) + { } ~ByteMathOption() override { snort_free(config.result_name); } @@ -96,6 +94,7 @@ public: private: const ByteMathData config; + int calc(uint32_t& value, const uint32_t rvalue); }; uint32_t ByteMathOption::hash() const @@ -110,7 +109,7 @@ uint32_t ByteMathOption::hash() const b += ((uint32_t) config.rvalue_var << 24 | (uint32_t) config.offset_var << 16 | (uint32_t) config.result_var << 8 | - config.endianess); + config.endianness); c += config.base; mix(a,b,c); @@ -129,24 +128,24 @@ uint32_t ByteMathOption::hash() const bool ByteMathOption::operator==(const IpsOption& ips) const { - if ( !IpsOption::operator==(ips) ) + if (!IpsOption::operator==(ips)) return false; const ByteMathOption& rhs = (const ByteMathOption&)ips; const ByteMathData* left = &config; const ByteMathData* right = &rhs.config; - if (( left->bytes_to_extract == right->bytes_to_extract) && - ( left->rvalue == right->rvalue) && - ( left->oper == right->oper) && - ( left->offset == right->offset) && - ( left->relative_flag == right->relative_flag) && - ( left->string_convert_flag == right->string_convert_flag) && - ( left->endianess == right->endianess) && - ( left->base == right->base) && - ( left->bitmask_val == right->bitmask_val) && - ( left->rvalue_var == right->rvalue_var) && - ( left->offset_var == right->offset_var) && + if (( left->bytes_to_extract == right->bytes_to_extract) and + ( left->rvalue == right->rvalue) and + ( left->oper == right->oper) and + ( left->offset == right->offset) and + ( left->relative_flag == right->relative_flag) and + ( left->string_convert_flag == right->string_convert_flag) and + ( left->endianness == right->endianness) and + ( left->base == right->base) and + ( left->bitmask_val == right->bitmask_val) and + ( left->rvalue_var == right->rvalue_var) and + ( left->offset_var == right->offset_var) and ( left->result_var == right->result_var)) { return true; @@ -163,18 +162,9 @@ IpsOption::EvalStatus ByteMathOption::eval(Cursor& c, Packet* p) { RuleProfile profile(byteMathPerfStats); - if (p == nullptr) - return NO_MATCH; - - const uint8_t* start = c.buffer(); - int dsize = c.size(); - - const uint8_t* ptr = config.relative_flag ? c.start() : c.buffer(); - const uint8_t* end = start + dsize; - /* Get values from ips options variables, if present. */ uint32_t rvalue; - if (config.rvalue_var >= 0 && config.rvalue_var < NUM_IPS_OPTIONS_VARS) + if (config.rvalue_var >= 0 and config.rvalue_var < NUM_IPS_OPTIONS_VARS) { GetVarValueByIndex(&rvalue, config.rvalue_var); if (rvalue == 0 and config.oper == BM_DIVIDE) @@ -184,13 +174,13 @@ IpsOption::EvalStatus ByteMathOption::eval(Cursor& c, Packet* p) rvalue = config.rvalue; int32_t offset; - if (config.offset_var >= 0 && config.offset_var < NUM_IPS_OPTIONS_VARS) + if (config.offset_var >= 0 and config.offset_var < NUM_IPS_OPTIONS_VARS) { // Rule options variables are kept as uint32_t, // in order to support full range for unsigned options. // Signed options do a cast to int32_t after getting the value. - // The range limitation should be taken into consideration when writing a rule - // with an option that is read from a variable. + // The range limitation should be taken into consideration when writing + // a rule with an option that is read from a variable. uint32_t extract_offset; GetVarValueByIndex(&extract_offset, config.offset_var); offset = (int32_t)extract_offset; @@ -198,50 +188,31 @@ IpsOption::EvalStatus ByteMathOption::eval(Cursor& c, Packet* p) else offset = config.offset; - ptr += offset; + ByteMathData extract_config = config; + extract_config.offset = offset; - // check bounds - if (ptr < start || ptr >= end) - return NO_MATCH; + uint32_t value = 0; + int bytes_read = extract_data(extract_config, c, p, value); - uint8_t endian = config.endianess; - if (config.endianess == ENDIAN_FUNC) - { - if (!p->endianness || - !p->endianness->get_offset_endianness(ptr - p->data, endian)) - return NO_MATCH; - } + if (bytes_read == NO_MATCH) + return NO_MATCH; - // do the extraction - uint32_t value; + if (calc(value, rvalue) == NO_MATCH) + return NO_MATCH; - if (!config.string_convert_flag) - { - if (byte_extract(endian, config.bytes_to_extract, ptr, start, end, &value) < 0) - return NO_MATCH; - } - else - { - if (string_extract(config.bytes_to_extract, config.base, ptr, start, end, &value) < 0) - return NO_MATCH; - } + SetVarValueByIndex(value, config.result_var); - if (config.bitmask_val != 0) - { - uint32_t num_tailing_zeros_bitmask = getNumberTailingZerosInBitmask(config.bitmask_val); - value = value & config.bitmask_val; - if ( value && num_tailing_zeros_bitmask ) - { - value = value >> num_tailing_zeros_bitmask; - } - } + return MATCH; +} +int ByteMathOption::calc(uint32_t& value, const uint32_t rvalue) +{ // Note: all of the operations are done on uint32_t. // If the rule isn't written correctly, there is a risk for wrap around. switch (config.oper) { case BM_PLUS: - if( value + rvalue < value ) + if (value + rvalue < value) { return NO_MATCH; } @@ -251,7 +222,7 @@ IpsOption::EvalStatus ByteMathOption::eval(Cursor& c, Packet* p) break; } case BM_MINUS: - if( value < rvalue ) + if (value < rvalue) { return NO_MATCH; } @@ -261,7 +232,8 @@ IpsOption::EvalStatus ByteMathOption::eval(Cursor& c, Packet* p) break; } case BM_MULTIPLY: - if ( value != 0 and rvalue != 0 and (((value * rvalue) / rvalue) != value) ) + if (value != 0 and rvalue != 0 and + (((value * rvalue) / rvalue) != value)) { return NO_MATCH; } @@ -270,18 +242,18 @@ IpsOption::EvalStatus ByteMathOption::eval(Cursor& c, Packet* p) value *= rvalue; break; } - case BM_DIVIDE: value /= rvalue; - break; + case BM_DIVIDE: + value /= rvalue; + break; - case BM_LEFT_SHIFT: value <<= rvalue; - break; + case BM_LEFT_SHIFT: + value <<= rvalue; + break; - case BM_RIGHT_SHIFT: value >>= rvalue; - break; + case BM_RIGHT_SHIFT: + value >>= rvalue; + break; } - - SetVarValueByIndex(value, config.result_var); - return MATCH; } @@ -300,7 +272,7 @@ static void parse_endian(uint8_t value, ByteMathData& idx) { assert(value <= 1); int endian[] = { ENDIAN_BIG, ENDIAN_LITTLE }; - set_byte_order(idx.endianess, endian[value], "byte_math"); + set_byte_order(idx.endianness, endian[value], "byte_math"); } //------------------------------------------------------------------------- @@ -358,7 +330,7 @@ public: { return DETECT; } public: - ByteMathData data = {}; + ByteMathData data{}; string rvalue_var; string off_var; }; @@ -374,16 +346,16 @@ bool ByteMathModule::begin(const char*, int, SnortConfig*) bool ByteMathModule::set(const char*, Value& v, SnortConfig*) { - if ( v.is("bytes") ) + if (v.is("bytes")) data.bytes_to_extract = v.get_uint8(); - else if ( v.is("oper") ) + else if (v.is("oper")) data.oper = (BM_Oper)v.get_uint8(); - else if ( v.is("rvalue") ) + else if (v.is("rvalue")) { long n; - if ( v.strtol(n) ) + if (v.strtol(n)) { if (n == 0) return false; @@ -392,32 +364,32 @@ bool ByteMathModule::set(const char*, Value& v, SnortConfig*) else rvalue_var = v.get_string(); } - else if ( v.is("offset") ) + else if (v.is("offset")) { long n; - if ( v.strtol(n) ) + if (v.strtol(n)) data.offset = n; else off_var = v.get_string(); } - else if ( v.is("relative") ) + else if (v.is("relative")) data.relative_flag = true; - else if ( v.is("dce") ) - set_byte_order(data.endianess, ENDIAN_FUNC, "byte_math"); + else if (v.is("dce")) + set_byte_order(data.endianness, ENDIAN_FUNC, "byte_math"); - else if ( v.is("string") ) + else if (v.is("string")) { data.string_convert_flag = true; parse_base(v.get_uint8(), data); } - else if ( v.is("endian") ) + else if (v.is("endian")) parse_endian(v.get_uint8(), data); - else if ( v.is("bitmask") ) + else if (v.is("bitmask")) data.bitmask_val = v.get_uint32(); - else if ( v.is("result") ) + else if (v.is("result")) data.result_name = snort_strdup(v.get_string()); else @@ -442,7 +414,7 @@ static bool ByteMathVerify(ByteMathData* data) return false; } - if ( ((data->oper == BM_LEFT_SHIFT) || (data->oper == BM_RIGHT_SHIFT)) && + if (((data->oper == BM_LEFT_SHIFT) or (data->oper == BM_RIGHT_SHIFT)) and (data->rvalue > 32)) { ParseError("Number of bits in rvalue input [%u] should be less than 32 " @@ -450,7 +422,7 @@ static bool ByteMathVerify(ByteMathData* data) return false; } - if (((data->oper == BM_LEFT_SHIFT) || (data->oper == BM_RIGHT_SHIFT)) && + if (((data->oper == BM_LEFT_SHIFT) or (data->oper == BM_RIGHT_SHIFT)) and (data->bytes_to_extract > 4)) { ParseError("for operators << and >> valid bytes_to_extract input range is" @@ -458,7 +430,7 @@ static bool ByteMathVerify(ByteMathData* data) return false; } - if (data->bytes_to_extract > MAX_BYTES_TO_GRAB && !data->string_convert_flag) + if (data->bytes_to_extract > MAX_BYTES_TO_GRAB and !data->string_convert_flag) { ParseError("byte_math rule option cannot extract more than %d bytes without valid" " string prefix.", MAX_BYTES_TO_GRAB); @@ -476,7 +448,7 @@ static bool ByteMathVerify(ByteMathData* data) bool ByteMathModule::end(const char*, int, SnortConfig*) { - if ( rvalue_var.empty() ) + if (rvalue_var.empty()) data.rvalue_var = IPS_OPTIONS_NO_VAR; else { @@ -489,7 +461,7 @@ bool ByteMathModule::end(const char*, int, SnortConfig*) } } - if ( off_var.empty() ) + if (off_var.empty()) data.offset_var = IPS_OPTIONS_NO_VAR; else { @@ -502,8 +474,8 @@ bool ByteMathModule::end(const char*, int, SnortConfig*) } } - if ( !data.endianess ) - data.endianess = ENDIAN_BIG; + if (!data.endianness ) + data.endianness = ENDIAN_BIG; return ByteMathVerify(&data); } @@ -575,3 +547,1007 @@ const BaseApi* ips_byte_math[] = &byte_math_api.base, nullptr }; + +//------------------------------------------------------------------------- +// UNIT TESTS +//------------------------------------------------------------------------- +#ifdef UNIT_TEST + +//------------------------------------------------------------------------- +// helpers +//------------------------------------------------------------------------- +#define INITIALIZE(obj, bytes_to_extract_value, rvalue_value, offset_value, bitmask_val_value, \ + result_name_value, oper_value, relative_flag_value, string_convert_flag_value, base_value, \ + endianness_value, result_var_value, rvalue_var_value, offset_var_value) \ + obj.base = base_value; \ + obj.bitmask_val = bitmask_val_value; \ + obj.bytes_to_extract = bytes_to_extract_value; \ + obj.offset = offset_value; \ + obj.endianness = endianness_value; \ + obj.relative_flag = relative_flag_value; \ + obj.string_convert_flag = string_convert_flag_value; \ + obj.rvalue = rvalue_value; \ + obj.oper = oper_value; \ + obj.offset_var = offset_var_value; \ + obj.result_var = result_var_value; \ + obj.rvalue_var = rvalue_var_value; \ + obj.result_name = result_name_value; + +class ByteMathDataMatcher + : public Catch::Matchers::Impl::MatcherBase +{ +public: + ByteMathDataMatcher(const ByteMathData& value) : m_value(value) {} + + bool match(ByteMathData const& rhs) const override + { + return ((m_value.bytes_to_extract == rhs.bytes_to_extract) and + (m_value.rvalue == rhs.rvalue) and (m_value.oper == rhs.oper) and + (m_value.offset == rhs.offset) and + (m_value.relative_flag == rhs.relative_flag) and + (m_value.string_convert_flag == rhs.string_convert_flag) and + (m_value.endianness == rhs.endianness) and + (m_value.base == rhs.base) and + (m_value.bitmask_val == rhs.bitmask_val) and + (m_value.rvalue_var == rhs.rvalue_var) and + (m_value.offset_var == rhs.offset_var) and + (m_value.result_var == rhs.result_var)); + } + + std::string describe() const override + { + std::ostringstream ss; + ss << "settings is equals to:\n"; + ss << "bytes_to_extract : " << m_value.bytes_to_extract << ";\n"; + ss << "rvalue : " << m_value.rvalue << ";\n"; + ss << "oper : " << m_value.oper << ";\n"; + ss << "offset : " << m_value.offset << ";\n"; + ss << "relative_flag : " << m_value.relative_flag << ";\n"; + ss << "string_convert_flag : " << m_value.string_convert_flag << ";\n"; + ss << "endianness : " << m_value.endianness << ";\n"; + ss << "base : " << m_value.base << ";\n"; + ss << "bitmask_val : " << m_value.bitmask_val << ";\n"; + ss << "rvalue_var : " << m_value.rvalue_var << ";\n"; + ss << "offset_var : " << m_value.offset_var << ";\n"; + ss << "result_var : " << m_value.result_var << ";\n"; + return ss.str(); + } + +private: + ByteMathData m_value; +}; + +ByteMathDataMatcher ByteMathDataEquals(const ByteMathData& value) +{ + return {value}; +} + +class SetBufferOptionHelper : public IpsOption +{ +public: + SetBufferOptionHelper(const char* value) + : IpsOption(value, RULE_OPTION_TYPE_BUFFER_SET) + { } +}; + +//------------------------------------------------------------------------- +// option tests +//------------------------------------------------------------------------- + +TEST_CASE("ByteMathOption::operator== valid", "[ips_byte_math]") +{ + SetBufferOptionHelper set_buf("test"); + + char* lhs_name = new char[9]; + strcpy(lhs_name, "test_lhs"); + ByteMathData data_lhs; + INITIALIZE(data_lhs, 0, 25, 0, 0, lhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG, + IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption lhs(data_lhs); + + char* rhs_name = new char[9]; + strcpy(rhs_name, "test_rhs"); + ByteMathData data_rhs; + INITIALIZE(data_rhs, 0, 25, 0, 0, rhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG, + IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption rhs(data_rhs); + + CHECK(lhs == rhs); +} + +TEST_CASE("ByteMathOption::operator== invalid", "[ips_byte_math]") +{ + SetBufferOptionHelper set_buf("test"); + + char* lhs_name = new char[5]; + strcpy(lhs_name, "test"); + ByteMathData data_lhs; + INITIALIZE(data_lhs, 0, 25, 0, 0, lhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG, + IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption lhs(data_lhs); + + char* rhs_name = new char[5]; + strcpy(rhs_name, "test"); + + SECTION("not equal to IpsOption object") + { + CHECK(lhs != set_buf); + delete[] rhs_name; + } + SECTION("all fields is different") + { + delete[] rhs_name; + rhs_name = new char[5]; + strcpy(rhs_name, "unix"); + ByteMathData data_rhs; + INITIALIZE(data_rhs, 2, 25, 2, 255, rhs_name, BM_MULTIPLY, 1, + 1, 8, ENDIAN_LITTLE, 1, 1, 1); + ByteMathOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("bytes_to_grab is different") + { + ByteMathData data_rhs; + INITIALIZE(data_rhs, 2, 25, 0, 0, rhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG, + IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("rvalue is different") + { + ByteMathData data_rhs; + INITIALIZE(data_rhs, 0, 15, 0, 0, rhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG, + IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("offset is different") + { + ByteMathData data_rhs; + INITIALIZE(data_rhs, 0, 25, 3, 0, rhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG, + IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("bitmask is different") + { + ByteMathData data_rhs; + INITIALIZE(data_rhs, 0, 25, 0, 255, rhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG, + IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("result_name is different") + { + delete[] rhs_name; + rhs_name = new char[5]; + strcpy(rhs_name, "unix"); + ByteMathData data_rhs; + INITIALIZE(data_rhs, 0, 25, 0, 255, rhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG, + IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("operation is different") + { + ByteMathData data_rhs; + INITIALIZE(data_rhs, 0, 25, 0, 0, rhs_name, BM_DIVIDE, 0, 0, 0, ENDIAN_BIG, + IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("relative_flag is different") + { + ByteMathData data_rhs; + INITIALIZE(data_rhs, 0, 25, 0, 0, rhs_name, BM_PLUS, 1, 0, 0, ENDIAN_BIG, + IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("string_convert_flag is different") + { + ByteMathData data_rhs; + INITIALIZE(data_rhs, 0, 25, 0, 0, rhs_name, BM_PLUS, 0, 1, 0, ENDIAN_BIG, + IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("base is different") + { + ByteMathData data_rhs; + INITIALIZE(data_rhs, 0, 25, 0, 0, rhs_name, BM_PLUS, 0, 0, 8, ENDIAN_BIG, + IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("endianness is different") + { + ByteMathData data_rhs; + INITIALIZE(data_rhs, 0, 25, 0, 0, rhs_name, BM_PLUS, 0, 0, 0, ENDIAN_LITTLE, + IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("result_var is different") + { + ByteMathData data_rhs; + INITIALIZE(data_rhs, 0, 25, 0, 0, rhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG, + 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("rvalue_var is different") + { + ByteMathData data_rhs; + INITIALIZE(data_rhs, 0, 25, 0, 0, rhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG, + IPS_OPTIONS_NO_VAR, 1, IPS_OPTIONS_NO_VAR); + ByteMathOption rhs(data_rhs); + CHECK(lhs != rhs); + } + SECTION("offset_var is different") + { + ByteMathData data_rhs; + INITIALIZE(data_rhs, 0, 25, 0, 0, rhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG, + IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, 0); + ByteMathOption rhs(data_rhs); + CHECK(lhs != rhs); + } +} + +TEST_CASE("ByteMathOption::hash", "[ips_byte_math]") +{ + SetBufferOptionHelper set_buf("test"); + + char* lhs_name = new char[5]; + strcpy(lhs_name, "test"); + ByteMathData data_lhs; + INITIALIZE(data_lhs, 2, 25, 2, 255, lhs_name, BM_MULTIPLY, 1, 1, 8, + ENDIAN_LITTLE, 1, 1, 1); + ByteMathOption lhs(data_lhs); + + SECTION("hash codes of any two equal objects are equal") + { + char* rhs_name = new char[5]; + strcpy(rhs_name, "test"); + ByteMathData data_rhs; + INITIALIZE(data_rhs, 2, 25, 2, 255, rhs_name, BM_MULTIPLY, 1, 1, 8, + ENDIAN_LITTLE, 1, 1, 1); + ByteMathOption rhs(data_rhs); + + CHECK(lhs.hash() == rhs.hash()); + } +} + +TEST_CASE("ByteMathOption::eval valid", "[ips_byte_math]") +{ + Packet p; + p.data = (const uint8_t*)"Lorem 12345"; + p.dsize = 11; + Cursor c(&p); + + for (unsigned i = 0; i < NUM_IPS_OPTIONS_VARS; ++i) + { + SetVarValueByIndex(0, i); + } + ClearIpsOptionsVars(); + + char* name = new char[5]; + strcpy(name, "test"); + + SECTION("1 byte read, all off, operation \"+\", rvalue 1") + { + ByteMathData data; + INITIALIZE(data, 1, 1, 0, 0, name, BM_PLUS, 0, 0, 0, + ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::MATCH); + uint32_t res = 0; + GetVarValueByIndex(&res, 0); + CHECK(res == 77); + } + SECTION("1 byte read, offset 3, operation \"*\", rvalue 2") + { + ByteMathData data; + INITIALIZE(data, 1, 2, 3, 0, name, BM_MULTIPLY, 0, 0, 0, + ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::MATCH); + uint32_t res = 0; + GetVarValueByIndex(&res, 0); + CHECK(res == 202); + } + SECTION("1 byte read, offset 3, relative, cursor 3, operation \"-\", rvalue 3") + { + ByteMathData data; + INITIALIZE(data, 1, 3, 3, 0, name, BM_MINUS, 1, 0, 0, + ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption opt(data); + c.set_pos(3); + CHECK(opt.eval(c, &p) == IpsOption::MATCH); + uint32_t res = 0; + GetVarValueByIndex(&res, 0); + CHECK(res == 46); + } + SECTION("cursor 3, 1 byte read, offset -3, relative, operation \"/\", rvalue 4") + { + ByteMathData data; + INITIALIZE(data, 1, 4, -3, 0, name, BM_DIVIDE, 1, 0, 0, + ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption opt(data); + c.set_pos(3); + CHECK(opt.eval(c, &p) == IpsOption::MATCH); + uint32_t res = 0; + GetVarValueByIndex(&res, 0); + CHECK(res == 19); + } + SECTION("1 byte read, offset 6, string conversion, base 10, operation \"+\", rvalue 4") + { + ByteMathData data; + INITIALIZE(data, 1, 4, 6, 0, name, BM_PLUS, 0, 1, 10, + ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::MATCH); + uint32_t res = 0; + GetVarValueByIndex(&res, 0); + CHECK(res == 5); + } + SECTION("1 byte read, offset 6, string conversion, base 10, operation \"<<\", rvalue 2") + { + ByteMathData data; + INITIALIZE(data, 1, 2, 6, 0, name, BM_LEFT_SHIFT, 0, 1, 10, + ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::MATCH); + uint32_t res = 0; + GetVarValueByIndex(&res, 0); + CHECK(res == 4); + } + SECTION("3 byte read, offset 6, string conversion, base 10, operation \">>\", rvalue 1") + { + ByteMathData data; + INITIALIZE(data, 3, 1, 6, 0, name, BM_RIGHT_SHIFT, 0, 1, 10, + ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::MATCH); + uint32_t res = 0; + GetVarValueByIndex(&res, 0); + CHECK(res == 61); + } + SECTION("2 bytes read, operation \">>\", result_var = 0, rvalue_var = 1") + { + SetVarValueByIndex(3, 1); + ByteMathData data; + INITIALIZE(data, 2, 0, 0, 0, name, BM_RIGHT_SHIFT, 0, + 0, 0, ENDIAN_BIG, 0, 1, IPS_OPTIONS_NO_VAR); + ByteMathOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::MATCH); + uint32_t res = 0; + GetVarValueByIndex(&res, 0); + CHECK(res == 2445); + } + SECTION("1 byte read, operation \"<<\", offset_var = 0, result_var = 1") + { + SetVarValueByIndex(1, 0); + ByteMathData data; + INITIALIZE(data, 1, 1, 0, 0, name, BM_LEFT_SHIFT, + 0, 0, 0, ENDIAN_BIG, 1, IPS_OPTIONS_NO_VAR, 0); + ByteMathOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::MATCH); + uint32_t res = 0; + GetVarValueByIndex(&res, 1); + CHECK(res == 222); + } +} + +TEST_CASE("ByteMathOption::eval invalid", "[ips_byte_math]") +{ + Packet p; + p.data = (const uint8_t*)"Lorem 9876"; + p.dsize = 11; + Cursor c(&p); + + for (unsigned i = 0; i < NUM_IPS_OPTIONS_VARS; ++i) + { + SetVarValueByIndex(0, i); + } + ClearIpsOptionsVars(); + + char* name = new char[5]; + strcpy(name, "test"); + + SECTION("rvalue_variable didn't exist") + { + ByteMathData data; + INITIALIZE(data, 1, 1, 0, 0, name, BM_PLUS, 0, + 0, 0, ENDIAN_BIG, 0, 1, IPS_OPTIONS_NO_VAR); + ByteMathOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::MATCH); + uint32_t res = 0; + GetVarValueByIndex(&res, 0); + CHECK(res == 76); + } + SECTION("offset_variable didn't exist") + { + ByteMathData data; + INITIALIZE(data, 1, 1, 1, 0, name, BM_PLUS, 0, + 0, 0, ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, 1); + ByteMathOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::MATCH); + uint32_t res = 0; + GetVarValueByIndex(&res, 0); + CHECK(res == 77); + } + SECTION("rvalue_variable_index > NUM_IPS_OPTIONS_VARS") + { + ByteMathData data; + INITIALIZE(data, 1, 1, 0, 0, name, BM_PLUS, 0, 0, 0, ENDIAN_BIG, + 0, NUM_IPS_OPTIONS_VARS + 1, IPS_OPTIONS_NO_VAR); + ByteMathOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::MATCH); + uint32_t res = 0; + GetVarValueByIndex(&res, 0); + CHECK(res == 77); + } + SECTION("offset_variable_index > NUM_IPS_OPTIONS_VARS") + { + ByteMathData data; + INITIALIZE(data, 1, 1, 1, 0, name, BM_PLUS, 0, 0, 0, ENDIAN_BIG, + 0, IPS_OPTIONS_NO_VAR, NUM_IPS_OPTIONS_VARS + 1); + ByteMathOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::MATCH); + uint32_t res = 0; + GetVarValueByIndex(&res, 0); + CHECK(res == 112); + } + SECTION("get negative number with MINUS") + { + ByteMathData data; + INITIALIZE(data, 1, 256, 0, 0, name, BM_MINUS, 0, 0, 0, ENDIAN_BIG, + 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::NO_MATCH); + } + SECTION("out of bounds of uint32_t, PLUS") + { + ByteMathData data; + INITIALIZE(data, 1, 4294967295, 0, 0, name, BM_PLUS, 0, 0, 0, + ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::NO_MATCH); + } + SECTION("out of bounds of uint32_t, MULTIPLY") + { + ByteMathData data; + INITIALIZE(data, 1, 2147483647, 0, 0, name, BM_MULTIPLY, 0, 0, 0, + ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + ByteMathOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::NO_MATCH); + } + SECTION("dividing on zero in rvalue_var") + { + SetVarValueByIndex(1, 0); + ByteMathData data; + INITIALIZE(data, 1, 0, 0, 0, name, BM_DIVIDE, 0, + 0, 0, ENDIAN_BIG, 0, 1, IPS_OPTIONS_NO_VAR); + ByteMathOption opt(data); + CHECK(opt.eval(c, &p) == IpsOption::NO_MATCH); + } +} + +//------------------------------------------------------------------------- +// module tests +//------------------------------------------------------------------------- + +TEST_CASE("ByteMathModule::begin", "[ips_byte_math]") +{ + ByteMathModule obj; + SECTION("test of \"begin\" method") + { + CHECK(obj.begin(nullptr, 0, nullptr)); + ByteMathData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 0, 0, 0, 0, 0, 0); + + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } +} + +TEST_CASE("ByteMathModule::end", "[ips_byte_math]") +{ + ByteMathModule obj; + + obj.begin(nullptr, 0, nullptr); + + Value v_name("test"); + Parameter p_name{"result", Parameter::PT_STRING, nullptr, nullptr, + "name of the variable to store the result"}; + v_name.set(&p_name); + obj.set(nullptr, v_name, nullptr); + + Value v_bytes(4.0); + Parameter p_bytes{"bytes", Parameter::PT_INT, "1:10", nullptr, + "number of bytes to pick up from the buffer"}; + v_bytes.set(&p_bytes); + obj.set(nullptr, v_bytes, nullptr); + + Value v_operation(0.0); + Parameter p_operation{"oper", Parameter::PT_ENUM, "+|-|*|/|<<|>>", + nullptr, "mathematical operation to perform"}; + v_operation.set(&p_operation); + obj.set(nullptr, v_operation, nullptr); + + char* name = new char[5]; + strcpy(name, "test"); + + SECTION("without variables") + { + Value v_rvalue("7"); + Parameter p_rvalue{"rvalue", Parameter::PT_STRING, nullptr, nullptr, + "value to use mathematical operation against"}; + v_rvalue.set(&p_rvalue); + obj.set(nullptr, v_rvalue, nullptr); + + CHECK(obj.end(nullptr, 0, nullptr)); + + ByteMathData expected; + INITIALIZE(expected, 4, 7, 0, 0, name, BM_PLUS, 0, 0, 0, ENDIAN_BIG, + 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR); + + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("with rvalue var") + { + ClearIpsOptionsVars(); + int8_t var_idx = AddVarNameToList("rvalue_test_var"); + SetVarValueByIndex(3, var_idx); + + Value v_rvalue("rvalue_test_var"); + Parameter p_rvalue{"rvalue", Parameter::PT_STRING, nullptr, nullptr, + "value to use mathematical operation against"}; + v_rvalue.set(&p_rvalue); + obj.set(nullptr, v_rvalue, nullptr); + + CHECK(obj.end(nullptr, 0, nullptr)); + + ByteMathData expected; + INITIALIZE(expected, 4, 0, 0, 0, name, BM_PLUS, 0, 0, 0, + ENDIAN_BIG, 0, var_idx, IPS_OPTIONS_NO_VAR); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("with offset variable") + { + ClearIpsOptionsVars(); + int8_t var_idx = AddVarNameToList("offset_test_var"); + SetVarValueByIndex(3, var_idx); + + Value v_offvalue("offset_test_var"); + Parameter p_offvalue{"offset", Parameter::PT_STRING, nullptr, nullptr, + "number of bytes into the buffer to start processing"}; + v_offvalue.set(&p_offvalue); + obj.set(nullptr, v_offvalue, nullptr); + + CHECK(obj.end(nullptr, 0, nullptr)); + + ByteMathData expected; + INITIALIZE(expected, 4, 0, 0, 0, name, BM_PLUS, 0, 0, + 0, ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, var_idx); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("rvalue var doesn't exist") + { + ClearIpsOptionsVars(); + Value v_rvalue("rvalue_test_var"); + Parameter p_rvalue{"rvalue", Parameter::PT_STRING, nullptr, nullptr, + "value to use mathematical operation against"}; + v_rvalue.set(&p_rvalue); + obj.set(nullptr, v_rvalue, nullptr); + + CHECK(!(obj.end(nullptr, 0, nullptr))); + } + SECTION("offset var doesn't exist") + { + ClearIpsOptionsVars(); + Value v_offvalue("offset_test_var"); + Parameter p_offvalue{"offset", Parameter::PT_STRING, nullptr, nullptr, + "number of bytes into the buffer to start processing"}; + v_offvalue.set(&p_offvalue); + obj.set(nullptr, v_offvalue, nullptr); + + CHECK(!(obj.end(nullptr, 0, nullptr))); + } + + delete[] obj.data.result_name; + delete[] name; +} + +TEST_CASE("Test of byte_math_ctor", "[ips_byte_math]") +{ + ClearIpsOptionsVars(); + + std::string name = "test"; + for (unsigned i = 0; i <= NUM_IPS_OPTIONS_VARS; ++i) + { + ByteMathModule obj; + obj.begin(nullptr, 0, nullptr); + Value v((name + std::to_string(i)).c_str()); + Parameter p{"result", Parameter::PT_STRING, nullptr, nullptr, + "name of the variable to store the result"}; + v.set(&p); + obj.set(nullptr, v, nullptr); + if (i < NUM_IPS_OPTIONS_VARS) + { + IpsOption* res = byte_math_ctor(&obj, nullptr); + delete res; + } + else + { + IpsOption* res_null = byte_math_ctor(&obj, nullptr); + CHECK(res_null == nullptr); + delete[] obj.data.result_name; + } + } +} + +TEST_CASE("ByteMathModule::set valid", "[ips_byte_math]") +{ + ByteMathModule obj; + obj.begin(nullptr, 0, nullptr); + + SECTION("set bytes") + { + Value v(4.0); + Parameter p{"bytes", Parameter::PT_INT, "1:10", nullptr, + "number of bytes to pick up from the buffer"}; + v.set(&p); + ByteMathData expected; + INITIALIZE(expected, 4, 0, 0, 0, 0, BM_PLUS, 0, 0, 0, 0, 0, 0, 0); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("set offset") + { + Value v("3"); + Parameter p{"offset", Parameter::PT_STRING, nullptr, nullptr, + "number of bytes into the buffer to start processing"}; + v.set(&p); + ByteMathData expected; + INITIALIZE(expected, 0, 0, 3, 0, 0, BM_PLUS, 0, 0, 0, 0, 0, 0, 0); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("set option \"+\"") + { + Value v(0.0); + Parameter p{"oper", Parameter::PT_ENUM, "+|-|*|/|<<|>>", nullptr, + "mathematical operation to perform"}; + v.set(&p); + ByteMathData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 0, 0, 0, 0, 0, 0); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("set option \"-\"") + { + Value v(1.0); + Parameter p{"oper", Parameter::PT_ENUM, "+|-|*|/|<<|>>", nullptr, + "mathematical operation to perform"}; + v.set(&p); + ByteMathData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, BM_MINUS, 0, 0, 0, 0, 0, 0, 0); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("set option \"*\"") + { + Value v(2.0); + Parameter p{"oper", Parameter::PT_ENUM, "+|-|*|/|<<|>>", nullptr, + "mathematical operation to perform"}; + v.set(&p); + ByteMathData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, BM_MULTIPLY, 0, 0, 0, 0, 0, 0, 0); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("set option \"/\"") + { + Value v(3.0); + Parameter p{"oper", Parameter::PT_ENUM, "+|-|*|/|<<|>>", nullptr, + "mathematical operation to perform"}; + v.set(&p); + ByteMathData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, BM_DIVIDE, 0, 0, 0, 0, 0, 0, 0); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("set option \"<<\"") + { + Value v(4.0); + Parameter p{"oper", Parameter::PT_ENUM, "+|-|*|/|<<|>>", nullptr, + "mathematical operation to perform"}; + v.set(&p); + ByteMathData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, BM_LEFT_SHIFT, 0, 0, 0, 0, 0, 0, 0); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("set option \">>\"") + { + Value v(5.0); + Parameter p{"oper", Parameter::PT_ENUM, "+|-|*|/|<<|>>", nullptr, + "mathematical operation to perform"}; + v.set(&p); + ByteMathData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, BM_RIGHT_SHIFT, 0, 0, 0, 0, 0, 0, 0); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("set rvalue num") + { + Value v("21"); + Parameter p{"rvalue", Parameter::PT_STRING, nullptr, nullptr, + "value to use mathematical operation against"}; + v.set(&p); + ByteMathData expected; + INITIALIZE(expected, 0, 21, 0, 0, 0, BM_PLUS, 0, 0, 0, 0, 0, 0, 0); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("set result") + { + Value v("res_name"); + Parameter p{"result", Parameter::PT_STRING, nullptr, nullptr, + "name of the variable to store the result"}; + v.set(&p); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data.result_name, Catch::Matchers::Equals("res_name")); + } + SECTION("set relative") + { + Value v(true); + Parameter p{"relative", Parameter::PT_IMPLIED, nullptr, nullptr, + "offset from cursor instead of start of buffer"}; + v.set(&p); + obj.set(nullptr, v, nullptr); + ByteMathData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 1, 0, 0, 0, 0, 0, 0); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("set endianness \"big\"") + { + Value v(0.0); + Parameter p{"endian", Parameter::PT_ENUM, "big|little", nullptr, + "specify big/little endian"}; + v.set(&p); + obj.set(nullptr, v, nullptr); + ByteMathData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 0, 0, ENDIAN_BIG, 0, 0, 0); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("set endianness \"little\"") + { + Value v(1.0); + Parameter p{"endian", Parameter::PT_ENUM, "big|little", nullptr, + "specify big/little endian"}; + v.set(&p); + ByteMathData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 0, 0, ENDIAN_LITTLE, 0, 0, 0); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("set dce") + { + Value v(true); + Parameter p{"dce", Parameter::PT_IMPLIED, nullptr, nullptr, + "dcerpc2 determines endianness"}; + v.set(&p); + ByteMathData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 0, 0, ENDIAN_FUNC, 0, 0, 0); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("set string, hex base") + { + Value v(0.0); + Parameter p{"string", Parameter::PT_ENUM, "hex|dec|oct", nullptr, + "convert extracted string to dec/hex/oct"}; + v.set(&p); + ByteMathData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 1, 16, 0, 0, 0, 0); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("set string, dec base") + { + Value v(1.0); + Parameter p{"string", Parameter::PT_ENUM, "hex|dec|oct", nullptr, + "convert extracted string to dec/hex/oct"}; + v.set(&p); + ByteMathData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 1, 10, 0, 0, 0, 0); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("set string, oct base") + { + Value v(2.0); + Parameter p{"string", Parameter::PT_ENUM, "hex|dec|oct", nullptr, + "convert extracted string to dec/hex/oct"}; + v.set(&p); + ByteMathData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 1, 8, 0, 0, 0, 0); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("set bitmask") + { + Value v(1023.0); + Parameter p{"bitmask", Parameter::PT_INT, "0x1:0xFFFFFFFF", nullptr, + "applies as bitwise AND to the extracted value before storage in 'name'"}; + v.set(&p); + ByteMathData expected; + INITIALIZE(expected, 0, 0, 0, 1023, 0, BM_PLUS, 0, 0, 0, 0, 0, 0, 0); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("rvalue as variable") + { + Value v("r_test_var"); + Parameter p{"rvalue", Parameter::PT_STRING, nullptr, nullptr, + "value to use mathematical operation against"}; + v.set(&p); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK(strcmp(obj.rvalue_var.c_str(), "r_test_var") == 0); + } + SECTION("offset as variable") + { + Value v("off_test_var"); + Parameter p{"offset", Parameter::PT_STRING, nullptr, nullptr, + "number of bytes into the buffer to start processing"}; + v.set(&p); + + CHECK(obj.set(nullptr, v, nullptr)); + CHECK(strcmp(obj.off_var.c_str(), "off_test_var") == 0); + } + + delete[] obj.data.result_name; +} + +TEST_CASE("ByteMathModule::set invalid", "[ips_byte_math]") +{ + ByteMathModule obj; + obj.begin(nullptr, 0, nullptr); + + SECTION("invalid parameter") + { + Value v(1023.0); + Parameter p{"error", Parameter::PT_INT, "nan", nullptr, + "not an option"}; + v.set(&p); + ByteMathData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 0, 0, 0, 0, 0, 0); + + CHECK(!obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } + SECTION("rvalue = 0") + { + Value v("0"); + Parameter p{"rvalue", Parameter::PT_STRING, nullptr, nullptr, + "value to use mathematical operation against"}; + v.set(&p); + ByteMathData expected; + INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 0, 0, 0, 0, 0, 0); + + CHECK(!obj.set(nullptr, v, nullptr)); + CHECK_THAT(obj.data, ByteMathDataEquals(expected)); + } +} +//------------------------------------------------------------------------- +// api tests +//------------------------------------------------------------------------- + +TEST_CASE("ByteMathVerify valid", "[ips_byte_math]") +{ + ByteMathData obj; + char name[] = "test"; + + SECTION("Minimum values, no string conversion") + { + INITIALIZE(obj, 1, 0, -65535, 0, name, BM_PLUS, 1, 0, 0, 0, 0, 0, 0); + CHECK(ByteMathVerify(&obj)); + } + SECTION("Maximum values, no string conversion") + { + INITIALIZE(obj, MAX_BYTES_TO_GRAB, 2147483647, 65535, 0xFFFFFFFF, name, BM_PLUS, 1, 0, + 0, ENDIAN_FUNC, NUM_IPS_OPTIONS_VARS, NUM_IPS_OPTIONS_VARS, NUM_IPS_OPTIONS_VARS); + CHECK(ByteMathVerify(&obj)); + } + SECTION("Minimum values, with string conversion") + { + INITIALIZE(obj, 1, 0, -65535, 0, name, BM_PLUS, 1, 1, 8, 0, 0, 0, 0); + CHECK(ByteMathVerify(&obj)); + } + SECTION("Maximum values, with string conversion") + { + INITIALIZE(obj, PARSELEN, 2147483647, 65535, 0xFFFFFFFF, name, BM_PLUS, 1, 1, 16, + ENDIAN_FUNC, NUM_IPS_OPTIONS_VARS, NUM_IPS_OPTIONS_VARS, NUM_IPS_OPTIONS_VARS); + CHECK(ByteMathVerify(&obj)); + } +} + +TEST_CASE("ByteMathVerify invalid", "[ips_byte_math]") +{ + char* name = new char[5]; + strcpy(name, "test"); + ByteMathData obj; + INITIALIZE(obj, 1, 9, 25, 1023, name, BM_PLUS, 1, 0, 0, 0, 0, 0, 0); + + SECTION("name existence check") + { + obj.result_name = nullptr; + + CHECK((!ByteMathVerify(&obj))); + } + SECTION("name not numeric check") + { + delete[] name; + name = new char[5]; + strcpy(name, "6in4"); + obj.result_name = name; + CHECK((!ByteMathVerify(&obj))); + } + SECTION("shift > 32 checks") + { + obj.rvalue = 33; + + obj.oper = BM_LEFT_SHIFT; + CHECK((!ByteMathVerify(&obj))); + + obj.oper = BM_RIGHT_SHIFT; + CHECK((!ByteMathVerify(&obj))); + } + SECTION("shift and bytes_to_extract > 4 checks") + { + obj.bytes_to_extract = MAX_BYTES_TO_GRAB + 1; + + obj.oper = BM_LEFT_SHIFT; + CHECK((!ByteMathVerify(&obj))); + + obj.oper = BM_RIGHT_SHIFT; + CHECK((!ByteMathVerify(&obj))); + } + SECTION("no string conversion and bytes_to_extract > 4 checks") + { + obj.bytes_to_extract = MAX_BYTES_TO_GRAB + 1; + CHECK((!ByteMathVerify(&obj))); + } + SECTION("bitmask checks") + { + obj.bytes_to_extract = 2; + obj.bitmask_val = 1048575; + CHECK((!ByteMathVerify(&obj))); + } + delete[] name; +} + +#endif diff --git a/src/ips_options/ips_byte_test.cc b/src/ips_options/ips_byte_test.cc index 377d575b5..f5a62255e 100644 --- a/src/ips_options/ips_byte_test.cc +++ b/src/ips_options/ips_byte_test.cc @@ -103,6 +103,11 @@ #include "protocols/packet.h" #include "utils/util.h" +#ifdef UNIT_TEST +#include +#include "catch/snort_catch.h" +#endif + #include "extract.h" using namespace snort; @@ -123,18 +128,11 @@ enum ByteTestOper CHECK_XOR }; -struct ByteTestData +struct ByteTestData : public ByteData { - uint32_t bytes_to_compare; uint32_t cmp_value; ByteTestOper opcode; - int32_t offset; bool not_flag; - bool relative_flag; - bool data_string_convert_flag; - uint8_t endianness; - uint32_t base; - uint32_t bitmask_val; int8_t cmp_value_var; int8_t offset_var; }; @@ -143,42 +141,43 @@ struct ByteTestData // static functions // ----------------------------------------------------------------------------- -static inline bool byte_test_check(ByteTestOper op, uint32_t val, uint32_t cmp, bool not_flag) +static inline bool byte_test_check(ByteTestOper op, uint32_t val, uint32_t cmp, + bool not_flag) { bool success = false; switch ( op ) { - case CHECK_LT: - success = (val < cmp); - break; - case CHECK_EQ: success = (val == cmp); break; - case CHECK_GT: - success = (val > cmp); + case CHECK_LT: + success = (val < cmp); break; - case CHECK_AND: - success = ((val & cmp) > 0); + case CHECK_GT: + success = (val > cmp); break; - case CHECK_XOR: - success = ((val ^ cmp) > 0); + case CHECK_LTE: + success = (val <= cmp); break; case CHECK_GTE: success = (val >= cmp); break; - case CHECK_LTE: - success = (val <= cmp); + case CHECK_AND: + success = ((val & cmp) > 0); + break; + + case CHECK_XOR: + success = ((val ^ cmp) > 0); break; } - if ( not_flag ) + if (not_flag) { success = !success; } @@ -189,8 +188,9 @@ static inline bool byte_test_check(ByteTestOper op, uint32_t val, uint32_t cmp, class ByteTestOption : public IpsOption { public: - ByteTestOption(const ByteTestData& c) : IpsOption(s_name, RULE_OPTION_TYPE_BUFFER_USE) - { config = c; } + ByteTestOption(const ByteTestData& c) : IpsOption(s_name, + RULE_OPTION_TYPE_BUFFER_USE), config(c) + { } uint32_t hash() const override; bool operator==(const IpsOption&) const override; @@ -210,7 +210,7 @@ private: uint32_t ByteTestOption::hash() const { - uint32_t a = config.bytes_to_compare; + uint32_t a = config.bytes_to_extract; uint32_t b = config.cmp_value; uint32_t c = config.opcode; @@ -219,7 +219,7 @@ uint32_t ByteTestOption::hash() const a += config.offset; b += config.not_flag ? (1 << 24) : 0; b += config.relative_flag ? (1 << 16) : 0; - b += config.data_string_convert_flag ? (1 << 8) : 0; + b += config.string_convert_flag ? (1 << 8) : 0; b += config.endianness; c += config.base; @@ -240,24 +240,24 @@ uint32_t ByteTestOption::hash() const bool ByteTestOption::operator==(const IpsOption& ips) const { - if ( !IpsOption::operator==(ips) ) + if (!IpsOption::operator==(ips)) return false; const ByteTestOption& rhs = (const ByteTestOption&)ips; const ByteTestData* left = &config; const ByteTestData* right = &rhs.config; - if (( left->bytes_to_compare == right->bytes_to_compare) && - ( left->cmp_value == right->cmp_value) && - ( left->opcode == right->opcode) && - ( left->offset == right->offset) && - ( left->not_flag == right->not_flag) && - ( left->relative_flag == right->relative_flag) && - ( left->data_string_convert_flag == right->data_string_convert_flag) && - ( left->endianness == right->endianness) && - ( left->base == right->base) && - ( left->cmp_value_var == right->cmp_value_var) && - ( left->offset_var == right->offset_var) && + if (( left->bytes_to_extract == right->bytes_to_extract) and + ( left->cmp_value == right->cmp_value) and + ( left->opcode == right->opcode) and + ( left->offset == right->offset) and + ( left->not_flag == right->not_flag) and + ( left->relative_flag == right->relative_flag) and + ( left->string_convert_flag == right->string_convert_flag) and + ( left->endianness == right->endianness) and + ( left->base == right->base) and + ( left->cmp_value_var == right->cmp_value_var) and + ( left->offset_var == right->offset_var) and ( left->bitmask_val == right->bitmask_val)) { return true; @@ -274,7 +274,7 @@ IpsOption::EvalStatus ByteTestOption::eval(Cursor& c, Packet* p) uint32_t cmp_value = 0; // Get values from byte_extract variables, if present. - if (btd->cmp_value_var >= 0 && btd->cmp_value_var < NUM_IPS_OPTIONS_VARS) + if (btd->cmp_value_var >= 0 and btd->cmp_value_var < NUM_IPS_OPTIONS_VARS) { uint32_t val; GetVarValueByIndex(&val, btd->cmp_value_var); @@ -285,7 +285,7 @@ IpsOption::EvalStatus ByteTestOption::eval(Cursor& c, Packet* p) int offset = 0; - if (btd->offset_var >= 0 && btd->offset_var < NUM_IPS_OPTIONS_VARS) + if (btd->offset_var >= 0 and btd->offset_var < NUM_IPS_OPTIONS_VARS) { uint32_t val; GetVarValueByIndex(&val, btd->offset_var); @@ -294,53 +294,21 @@ IpsOption::EvalStatus ByteTestOption::eval(Cursor& c, Packet* p) else offset = btd->offset; - const uint8_t* start_ptr = btd->relative_flag ? c.start() : c.buffer(); - start_ptr += offset; + unsigned len = btd->relative_flag ? c.length() : c.size(); + if (len > btd->bytes_to_extract) + len = btd->bytes_to_extract; - uint8_t endian = btd->endianness; - if (endian == ENDIAN_FUNC) - { - if (!p->endianness || - !p->endianness->get_offset_endianness(start_ptr - p->data, endian)) - return NO_MATCH; - } + ByteTestData extract_config = *btd; + extract_config.bytes_to_extract = len; + extract_config.offset = offset; uint32_t value = 0; + int32_t payload_bytes_grabbed = extract_data(extract_config, c, p, value); - if (!btd->data_string_convert_flag) - { - if ( byte_extract( - endian, btd->bytes_to_compare, - start_ptr, c.buffer(), c.endo(), &value)) - return NO_MATCH; - } - else - { - unsigned len = btd->relative_flag ? c.length() : c.size(); - - if ( len > btd->bytes_to_compare ) - len = btd->bytes_to_compare; + if (payload_bytes_grabbed == NO_MATCH) + return NO_MATCH; - int payload_bytes_grabbed = string_extract( - len, btd->base, start_ptr, c.buffer(), c.endo(), &value); - - if ( payload_bytes_grabbed < 0 ) - { - return NO_MATCH; - } - } - - if (btd->bitmask_val != 0 ) - { - uint32_t num_tailing_zeros_bitmask = getNumberTailingZerosInBitmask(btd->bitmask_val); - value = value & btd->bitmask_val; - if ( value && num_tailing_zeros_bitmask ) - { - value = value >> num_tailing_zeros_bitmask; - } - } - - if ( byte_test_check(btd->opcode, value, cmp_value, btd->not_flag) ) + if (byte_test_check(btd->opcode, value, cmp_value, btd->not_flag)) return MATCH; return NO_MATCH; @@ -360,7 +328,7 @@ static void parse_operator(const char* oper, ByteTestData& idx) cptr++; } - if (idx.not_flag && strlen(cptr) == 0) + if (idx.not_flag and strlen(cptr) == 0) { idx.opcode = CHECK_EQ; } @@ -426,7 +394,7 @@ static const Parameter s_params[] = "variable name or value to test the converted result against" }, { "~offset", Parameter::PT_STRING, nullptr, nullptr, - "variable name or number of bytes into the payload to start processing" }, + "variable name or number of bytes into the payload to start processing"}, { "relative", Parameter::PT_IMPLIED, nullptr, nullptr, "offset from cursor instead of start of buffer" }, @@ -492,7 +460,7 @@ bool ByteTestModule::begin(const char*, int, SnortConfig*) bool ByteTestModule::end(const char*, int, SnortConfig*) { - if ( off_var.empty() ) + if (off_var.empty()) data.offset_var = IPS_OPTIONS_NO_VAR; else { @@ -504,7 +472,7 @@ bool ByteTestModule::end(const char*, int, SnortConfig*) return false; } } - if ( cmp_var.empty() ) + if (cmp_var.empty()) data.cmp_value_var = IPS_OPTIONS_NO_VAR; else { @@ -516,12 +484,13 @@ bool ByteTestModule::end(const char*, int, SnortConfig*) return false; } } - if ( !data.endianness ) + if (!data.endianness) data.endianness = ENDIAN_BIG; - if (numBytesInBitmask(data.bitmask_val) > data.bytes_to_compare) + if (numBytesInBitmask(data.bitmask_val) > data.bytes_to_extract) { - ParseError("Number of bytes in \"bitmask\" value is greater than bytes to extract."); + ParseError("Number of bytes in \"bitmask\" value is greater " \ + "than bytes to extract."); return false; } @@ -530,55 +499,55 @@ bool ByteTestModule::end(const char*, int, SnortConfig*) bool ByteTestModule::set(const char*, Value& v, SnortConfig*) { - if ( v.is("~count") ) - data.bytes_to_compare = v.get_uint8(); + if (v.is("~count")) + data.bytes_to_extract = v.get_uint8(); - else if ( v.is("~operator") ) + else if (v.is("~operator")) parse_operator(v.get_string(), data); - else if ( v.is("~compare") ) + else if (v.is("~compare")) { long n; - if ( v.strtol(n) ) + if (v.strtol(n)) data.cmp_value = n; else cmp_var = v.get_string(); } - else if ( v.is("~offset") ) + else if (v.is("~offset")) { long n; - if ( v.strtol(n) ) + if (v.strtol(n)) data.offset = n; else off_var = v.get_string(); } - else if ( v.is("relative") ) + else if (v.is("relative")) data.relative_flag = true; - else if ( v.is("big") ) + else if (v.is("big")) set_byte_order(data.endianness, ENDIAN_BIG, "byte_test"); - else if ( v.is("little") ) + else if (v.is("little")) set_byte_order(data.endianness, ENDIAN_LITTLE, "byte_test"); - else if ( v.is("dce") ) + else if (v.is("dce")) set_byte_order(data.endianness, ENDIAN_FUNC, "byte_test"); - else if ( v.is("string") ) + else if (v.is("string")) { - data.data_string_convert_flag = true; + data.string_convert_flag = true; data.base = 10; } - else if ( v.is("dec") ) + else if (v.is("dec")) data.base = 10; - else if ( v.is("hex") ) + else if (v.is("hex")) data.base = 16; - else if ( v.is("oct") ) + else if (v.is("oct")) data.base = 8; - else if ( v.is("bitmask") ) + else if (v.is("bitmask")) data.bitmask_val = v.get_uint32(); else @@ -647,3 +616,496 @@ const BaseApi* ips_byte_test[] = nullptr }; +//------------------------------------------------------------------------- +// UNIT TESTS +//------------------------------------------------------------------------- +#ifdef UNIT_TEST +#include + +#include "catch/snort_catch.h" + +#define NO_MATCH snort::IpsOption::EvalStatus::NO_MATCH +#define MATCH snort::IpsOption::EvalStatus::MATCH + +void SetByteTestData(ByteTestData &byte_test, int value, ByteTestOper code = CHECK_EQ) +{ + byte_test.bytes_to_extract = value; + byte_test.cmp_value = value; + byte_test.opcode = code; + byte_test.offset = value; + byte_test.not_flag = value; + byte_test.relative_flag = value; + byte_test.string_convert_flag = value; + byte_test.endianness = value; + byte_test.base = value; + byte_test.bitmask_val = value; + byte_test.cmp_value_var = value; + byte_test.offset_var = value; +} + +void SetByteTestDataMax(ByteTestData& byte_test) +{ + byte_test.bytes_to_extract = UINT_MAX; + byte_test.cmp_value = UINT_MAX; + byte_test.opcode = CHECK_XOR; + byte_test.offset = INT_MAX; + byte_test.not_flag = true; + byte_test.relative_flag = true; + byte_test.string_convert_flag = true; + byte_test.endianness = UCHAR_MAX; + byte_test.base = UINT_MAX; + byte_test.bitmask_val = UINT_MAX; + byte_test.cmp_value_var = CHAR_MAX; + byte_test.offset_var = CHAR_MAX; +} + +class StubIpsOption : public IpsOption +{ +public: + StubIpsOption(const char* name, option_type_t option_type) : + IpsOption(name, option_type) + { }; + +}; + +class StubEndianness : public Endianness +{ +public: + StubEndianness() = default; + virtual bool get_offset_endianness(int32_t, uint8_t& ) override + { return false; } +}; + +TEST_CASE("byte_test_check test", "[ips_byte_test]") +{ + SECTION("Incorrect ByteTestOper, other data correct") + { + REQUIRE(byte_test_check(ByteTestOper(7), 1, 1, 0) == false); + } + + SECTION("Incorrect ByteTestOper, true not_flag") + { + REQUIRE(byte_test_check(ByteTestOper(7), 1, 1, 1) == true); + } + + SECTION("CHECK_EQ both true && false situation") + { + REQUIRE(byte_test_check(ByteTestOper(0), 1, 1, 0) == true); + REQUIRE(byte_test_check(ByteTestOper(0), 1, 2, 0) == false); + } + + SECTION("CHECK_LT both true && false situation") + { + REQUIRE(byte_test_check(ByteTestOper(1), 1, 2, 0) == true); + REQUIRE(byte_test_check(ByteTestOper(1), 4, 1, 0) == false); + } + + SECTION("CHECK_GT both true && false situation") + { + REQUIRE(byte_test_check(ByteTestOper(2), 2, 1, 0) == true); + REQUIRE(byte_test_check(ByteTestOper(2), 1, 4, 0) == false); + } + + SECTION("CHECK_LTE both true && false situation") + { + REQUIRE(byte_test_check(ByteTestOper(3), 0, 1, 0) == true); + REQUIRE(byte_test_check(ByteTestOper(3), 4, 1, 0) == false); + } + + SECTION("CHECK_GTE both true && false situation") + { + REQUIRE(byte_test_check(ByteTestOper(4), 1, 0, 0) == true); + REQUIRE(byte_test_check(ByteTestOper(4), 0, 4, 0) == false); + } + + SECTION("CHECK_AND for bites both true && false situation") + { + REQUIRE(byte_test_check(ByteTestOper(5), 1, 1, 0) == true); + REQUIRE(byte_test_check(ByteTestOper(5), 1, 0, 0) == false); + } + + SECTION("CHECK_XOR for bites both true && false situation") + { + REQUIRE(byte_test_check(ByteTestOper(6), 1, 0, 0) == true); + REQUIRE(byte_test_check(ByteTestOper(6), 1, 1, 0) == false); + } +} + +TEST_CASE("ByteTestOption test", "[ips_byte_test]") +{ + ByteTestData byte_test; + SetByteTestData(byte_test, 1); + snort::IpsOption::set_buffer("hello_world"); + + SECTION("method hash") + { + ByteTestOption hash_test(byte_test); + ByteTestOption hash_test_equal(byte_test); + + SECTION("Testing hash with very low values") + { + SECTION("Hash has same source") + { + CHECK(hash_test.hash() == hash_test_equal.hash()); + } + + SECTION("Compare hash from different source") + { + SetByteTestData(byte_test, 4); + ByteTestOption hash_test_diff(byte_test); + CHECK(hash_test.hash() != hash_test_diff.hash()); + } + } + + SECTION("Testing hash with maximum values") + { + SetByteTestDataMax(byte_test); + ByteTestOption hash_test_max(byte_test); + ByteTestOption hash_test_equal_max(byte_test); + + SECTION("Hash has same source") + { + CHECK(hash_test_max.hash() == hash_test_equal_max.hash()); + } + + SECTION("Testing hash with maximum values from different source") + { + SetByteTestDataMax(byte_test); + ByteTestOption hash_test_max(byte_test); + CHECK(hash_test.hash() != hash_test_max.hash()); + } + } + } + + SECTION("operator ==") + { + ByteTestOption test(byte_test); + + SECTION("Compare IpsOptions with different names") + { + StubIpsOption case_diff_name("not_hello_world", + option_type_t::RULE_OPTION_TYPE_BUFFER_USE); + REQUIRE(test != case_diff_name); + } + + SECTION("Compare between equals objects") + { + ByteTestOption test_1(byte_test); + REQUIRE(test == test_1); + } + + SECTION("byte_to_compare is different") + { + byte_test.bytes_to_extract = 2; + ByteTestOption test_2_1(byte_test); + REQUIRE(test != test_2_1); + } + + SECTION("cmp_value is different") + { + byte_test.cmp_value = 2; + ByteTestOption test_2_2(byte_test); + REQUIRE(test != test_2_2); + } + + SECTION("cmp_value is different") + { + byte_test.opcode = CHECK_LT; + ByteTestOption test_2_3(byte_test); + REQUIRE(test != test_2_3); + } + + SECTION("offset is different") + { + byte_test.offset = 2; + ByteTestOption test_2_4(byte_test); + REQUIRE(test != test_2_4); + } + + SECTION("not_flag is different") + { + byte_test.not_flag = 0; + ByteTestOption test_2_5(byte_test); + REQUIRE(test != test_2_5); + } + + SECTION("relative_flag is different") + { + byte_test.relative_flag = 0; + ByteTestOption test_2_6(byte_test); + REQUIRE(test != test_2_6); + } + + SECTION("string_convert_flag is different") + { + byte_test.string_convert_flag = 0; + ByteTestOption test_2_7(byte_test); + REQUIRE(test != test_2_7); + } + + SECTION("endianness is different") + { + byte_test.endianness = 0; + ByteTestOption test_2_8(byte_test); + REQUIRE(test != test_2_8); + } + + SECTION("base is different") + { + byte_test.base = 2; + ByteTestOption test_2_9(byte_test); + REQUIRE(test != test_2_9); + } + + SECTION("bitmask_val is different") + { + byte_test.bitmask_val = 2; + ByteTestOption test_2_10(byte_test); + REQUIRE(test != test_2_10); + } + + SECTION("cmp_value_var is different") + { + byte_test.cmp_value_var = 0; + ByteTestOption test_2_13(byte_test); + REQUIRE(test != test_2_13); + } + + SECTION("cmp_value_var is different") + { + byte_test.offset_var = 0; + ByteTestOption test_2_12(byte_test); + REQUIRE(test != test_2_12); + } + } + + SECTION("method eval") + { + Packet test_packet; + Cursor current_cursor; + SetByteTestData(byte_test, 1); + + SECTION("Cursor not set correct for byte_extract") + { + byte_test.cmp_value_var = 3; + byte_test.offset_var = 3; + byte_test.string_convert_flag = 0; + ByteTestOption test_2(byte_test); + REQUIRE((test_2.eval(current_cursor, &test_packet)) == NO_MATCH); + } + + SECTION("Byte_to_compare set to zero for string_extract") + { + byte_test.string_convert_flag = 1; + byte_test.bytes_to_extract = 0; + ByteTestOption test_3(byte_test); + uint8_t buff = 0; + current_cursor.set("hello_world_long_name", &buff, 50); + REQUIRE((test_3.eval(current_cursor, &test_packet)) == NO_MATCH); + } + + SECTION("Byte_test_check with extract value not equal to need one") + { + byte_test.string_convert_flag = 0; + byte_test.relative_flag = 0; + uint8_t buff = 0; + current_cursor.set("hello_world_long_name", &buff, 50); + ByteTestOption test_4(byte_test); + REQUIRE((test_4.eval(current_cursor, &test_packet)) == NO_MATCH); + } + + SECTION("Correct match") + { + byte_test.string_convert_flag = 0; + byte_test.relative_flag = 0; + byte_test.opcode = ByteTestOper(7); + byte_test.not_flag = 1; + uint8_t buff = 0; + current_cursor.set("hello_world_long_name", &buff, 50); + ByteTestOption test_5(byte_test); + REQUIRE((test_5.eval(current_cursor, &test_packet)) == MATCH); + } + } + +} + +TEST_CASE("ByteTestModule test", "[ips_byte_test]") +{ + ByteTestModule module_test; + ByteTestData byte_test; + SetByteTestData(byte_test, 1); + + SECTION("method end") + { + std::string buff = "tmp"; + + SECTION("Undefined rule option for var") + { + module_test.cmp_var = buff; + module_test.data = byte_test; + REQUIRE(module_test.end("tmp", 0, nullptr) == false); + } + + SECTION("Undefined rule option for offset_var") + { + module_test.cmp_var.clear(); + module_test.off_var = buff; + module_test.data = byte_test; + REQUIRE(module_test.end("tmp", 0, nullptr) == false); + } + + SECTION("Number of bytes in \"bitmask\" value is greater than bytes to extract") + { + byte_test.endianness = 0; + byte_test.bytes_to_extract = 0; + module_test.data = byte_test; + REQUIRE(module_test.end("tmp", 0, nullptr) == false); + } + + SECTION("Case with returned value true") + { + module_test.data = byte_test; + REQUIRE(module_test.end("tmp", 0, nullptr) == true); + } + } + + SECTION("method set") + { + Value value(false); + + SECTION("All params incorrect") + { + REQUIRE(module_test.set(nullptr, value, nullptr) == false); + } + + SECTION("Case param \"~count\"") + { + Parameter param("~count", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_test.set(nullptr, value, nullptr) == true); + } + + SECTION("Param \"~operator\" correct") + { + Parameter param("~operator", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_test.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"~compare\"") + { + SECTION("Value doesn't have a str") + { + Parameter param("~compare", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_test.set(nullptr, value, nullptr) == true); + } + + SECTION("When value has a str") + { + Value value_tmp("123"); + Parameter param("~compare", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value_tmp.set(¶m); + REQUIRE(module_test.set(nullptr, value_tmp, nullptr) == true); + } + } + + SECTION("Case param \"~offset\"") + { + SECTION("Value doesn't have a str") + { + Parameter param("~offset", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_test.set(nullptr, value, nullptr) == true); + } + + SECTION("When value has a str") + { + Value value_tmp("123"); + Parameter param("~offset", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value_tmp.set(¶m); + REQUIRE(module_test.set(nullptr, value_tmp, nullptr) == true); + } + } + + SECTION("Case param \"relative\"") + { + Parameter param("relative", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_test.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"big\"") + { + Parameter param("big", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_test.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"little\"") + { + Parameter param("little", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_test.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"dce\"") + { + Parameter param("dce", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_test.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"string\"") + { + Parameter param("string", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_test.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"dec\"") + { + Parameter param("dec", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_test.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"hex\"") + { + Parameter param("hex", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_test.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"oct\"") + { + Parameter param("oct", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_test.set(nullptr, value, nullptr) == true); + } + + SECTION("Case param \"bitmask\"") + { + Parameter param("bitmask", snort::Parameter::Type::PT_BOOL, + nullptr, "default", "help"); + value.set(¶m); + REQUIRE(module_test.set(nullptr, value, nullptr) == true); + } + } +} + +#endif