From: Pranav Bhalerao (prbhaler) Date: Mon, 11 Oct 2021 08:59:24 +0000 (+0000) Subject: Merge pull request #3022 in SNORT/snort3 from ~AMARNAYA/snort3:feature_vba_macrodata... X-Git-Tag: 3.1.15.0~13 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b153ff7fef7027b6b88bf3188c4f0d428e41554b;p=thirdparty%2Fsnort3.git Merge pull request #3022 in SNORT/snort3 from ~AMARNAYA/snort3:feature_vba_macrodata to master Squashed commit of the following: commit a6e4992d0bf97781fdefc90fe89571c4210f574c Author: Steve Chew (stechew) Date: Mon Jul 19 21:49:09 2021 +0000 decompress, http_inspect: Add support for processing ole files and for vba_data ips option --- diff --git a/doc/reference/snort_reference.text b/doc/reference/snort_reference.text index 81b3229fb..370ba5c76 100644 --- a/doc/reference/snort_reference.text +++ b/doc/reference/snort_reference.text @@ -264,8 +264,9 @@ Table of Contents 7.115. tos 7.116. ttl 7.117. urg - 7.118. window - 7.119. wscale + 7.118. vba_data + 7.119. window + 7.120. wscale 8. Search Engine Modules 9. SO Rule Modules @@ -3682,6 +3683,8 @@ Configuration: response bodies * bool http_inspect.decompress_zip = false: decompress zip files in response bodies + * bool http_inspect.decompress_vba = false: decompress vba macro + data of MS office files in response bodies * bool http_inspect.script_detection = false: inspect JavaScript immediately upon script end * bool http_inspect.normalize_javascript = false: use legacy @@ -7907,7 +7910,18 @@ Configuration: { 0:65535 } -7.118. window +7.118. vba_data + +-------------- + +Help: rule option to set the detection cursor to the MS office visual basic for applications macros buffer + +Type: ips_option + +Usage: detect + + +7.119. window -------------- @@ -7923,7 +7937,7 @@ Configuration: range { 0:65535 } -7.119. wscale +7.120. wscale -------------- @@ -9143,6 +9157,8 @@ these libraries see the Getting Started section of the manual. response bodies * bool http_inspect.decompress_zip = false: decompress zip files in response bodies + * bool http_inspect.decompress_vba = false: decompress vba macro + data of MS office files in response bodies * string http_inspect.ignore_unreserved: do not alert when the specified unreserved characters are percent-encoded in a URI.Unreserved characters are 0-9, a-z, A-Z, period, underscore, @@ -14819,6 +14835,8 @@ and are not applicable elsewhere. * unified2 (logger): output event and packet in unified2 format file * urg (ips_option): detection for TCP urgent pointer + * vba_data (ips_option): rule option to set the detection cursor to + the MS Office Visual Basic for Applications macros buffer * vlan (codec): support for local area network * window (ips_option): rule option to check TCP window field * wizard (inspector): inspector that implements port-independent @@ -15147,6 +15165,8 @@ and are not applicable elsewhere. * ips_option::tos: rule option to check type of service field * ips_option::ttl: rule option to check time to live field * ips_option::urg: detection for TCP urgent pointer + * ips_option::vba_data: rule option to set the detection cursor to + the MS Office Visual Basic for Applications macros buffer * ips_option::window: rule option to check TCP window field * ips_option::wscale: detection for TCP window scale * logger::alert_csv: output event in csv format diff --git a/doc/user/http_inspect.txt b/doc/user/http_inspect.txt index 82b264d96..c03215bb8 100755 --- a/doc/user/http_inspect.txt +++ b/doc/user/http_inspect.txt @@ -135,6 +135,15 @@ content is decompressed and made available through the file data rule option. The compressed SWF file signature is converted to FWS to indicate an uncompressed file. +===== decompress_vba + +decompress_vba = true will enable decompression of RLE (Run Length Encoding) +compressed vba (Visual Basic for Applications) macro data of MS Office +files. The MS office files are PKZIP compressed which are parsed to locate +the OLE (Object Linking and Embedding) file embedded with the files +containing RLE compressed vba macro data. The decompressed vba macro data is +then made available through the vba_data ips rule option. + ===== normalize_javascript normalize_javascript = true will enable legacy normalizer of JavaScript within @@ -601,6 +610,12 @@ js_normalization_depth option is described above. Despite what js_data has, file_data still contains the whole HTTP body with an original JavaScript in it. +===== vba_data + +The vba_data will contain the decompressed Visual Basic for Applications +(vba) macro data embedded in MS office files. It requires decompress_zip +and decompress_vba options enabled. + ==== Timing issues and combining rule options HTTP inspector is stateful. That means it is aware of a bigger picture than diff --git a/doc/user/snort_user.text b/doc/user/snort_user.text index 303e9eab2..d2cf9f9a2 100644 --- a/doc/user/snort_user.text +++ b/doc/user/snort_user.text @@ -3930,7 +3930,16 @@ LZMA. The compressed content is decompressed and made available through the file data rule option. The compressed SWF file signature is converted to FWS to indicate an uncompressed file. -5.10.2.7. normalize_javascript +5.10.2.7. decompress_vba + +decompress_vba = true will enable decompression of RLE (Run Length Encoding) +compressed vba (Visual Basic for Applications) macro data of MS Office +files. The MS office files are PKZIP compressed which are parsed to locate +the OLE (Object Linking and Embedding) file embedded with the files +containing RLE compressed vba macro data. The decompressed vba macro data is +then made available through the vba_data ips rule option. + +5.10.2.8. normalize_javascript normalize_javascript = true will enable legacy normalizer of JavaScript within the HTTP response body. http_inspect looks for @@ -3945,7 +3954,7 @@ normalizations refer to basic JavaScript normalization. Cannot be used together with js_normalization_depth (doing so will cause Snort to fail to load). This is planned to be deprecated at some point. -5.10.2.8. js_normalization_depth +5.10.2.9. js_normalization_depth js_normalization_depth = N {-1 : max53} will set a number of input JavaScript bytes to normalize and enable the enhanced normalizer. The @@ -3966,7 +3975,7 @@ function names. The normalized data is available through the js_data rule option. This is currently experimental and still under development. -5.10.2.9. js_norm_identifier_depth +5.10.2.10. js_norm_identifier_depth js_norm_identifier_depth = N {0 : 260000} will set a number of unique JavaScript identifiers to normalize. When the depth is reached, a @@ -3981,7 +3990,7 @@ js_normalization_depth is set to a non-zero value, enabling the enhanced normalizer. This is currently experimental and still under development. -5.10.2.10. js_norm_max_tmpl_nest +5.10.2.11. js_norm_max_tmpl_nest js_norm_max_tmpl_nest = N {0 : 255} (default 32) is an option of the enhanced JavaScript normalizer that determines the deepest level of @@ -3995,7 +4004,7 @@ tracking. This option is used only when js_normalization_depth is not 0. This feature is currently experimental and still under development. -5.10.2.11. xff_headers +5.10.2.12. xff_headers This configuration supports defining custom x-forwarded-for type headers. In a multi-vendor world, it is quite possible that the @@ -4010,7 +4019,7 @@ they are defined, e.g "x-forwarded-for" will be preferred than "true-client-ip" if both headers are present in the stream. The header names should be delimited by a space. -5.10.2.12. maximum_host_length +5.10.2.13. maximum_host_length Setting maximum_host_length causes http_inspect to generate 119:25 if the Host header value including optional white space exceeds the @@ -4018,7 +4027,7 @@ specified length. In the abnormal case of multiple Host headers, the total length of the combined values is used. The default value is -1, meaning do not perform this check. -5.10.2.13. maximum_chunk_length +5.10.2.14. maximum_chunk_length http_inspect strictly limits individual chunks within a chunked message body to be less than four gigabytes. @@ -4026,7 +4035,7 @@ message body to be less than four gigabytes. A lower limit may be configured by setting maximum_chunk_length. Any chunk longer than maximum chunk length will generate a 119:16 alert. -5.10.2.14. URI processing +5.10.2.15. URI processing Normalization and inspection of the URI in the HTTP request message is a key aspect of what http_inspect does. The best way to normalize @@ -4427,6 +4436,12 @@ js_normalization_depth option is described above. Despite what js_data has, file_data still contains the whole HTTP body with an original JavaScript in it. +5.10.5.15. vba_data + +The vba_data will contain the decompressed Visual Basic for Applications +(vba) macro data embedded in MS office files. It requires decompress_zip +and decompress_vba options enabled. + 5.10.6. Timing issues and combining rule options HTTP inspector is stateful. That means it is aware of a bigger diff --git a/src/decompress/CMakeLists.txt b/src/decompress/CMakeLists.txt index 21ba69be9..e42179c9a 100644 --- a/src/decompress/CMakeLists.txt +++ b/src/decompress/CMakeLists.txt @@ -1,4 +1,6 @@ +add_subdirectory (test) + set( DECOMPRESS_INCLUDES file_decomp.h ) @@ -12,8 +14,13 @@ add_library (decompress OBJECT file_decomp_swf.h file_decomp_zip.cc file_decomp_zip.h + file_olefile.cc + file_olefile.h + file_oleheader.cc + file_oleheader.h ) install (FILES ${DECOMPRESS_INCLUDES} DESTINATION "${INCLUDE_INSTALL_PATH}/decompress" ) + diff --git a/src/decompress/file_decomp.cc b/src/decompress/file_decomp.cc index 957196ee4..78371587d 100644 --- a/src/decompress/file_decomp.cc +++ b/src/decompress/file_decomp.cc @@ -231,6 +231,9 @@ static fd_status_t Process_Decompression(fd_session_t* SessionPtr) } case ( FILE_TYPE_ZIP ): { + if (SessionPtr->Modes & FILE_VBA_EXTR_BIT) + SessionPtr->vba_analysis = true; + Ret_Code = File_Decomp_ZIP(SessionPtr); break; } @@ -298,6 +301,9 @@ fd_session_t* File_Decomp_New() New_Session->Avail_Out = 0; New_Session->Next_Out = nullptr; New_Session->File_Type = FILE_TYPE_NONE; + New_Session->vba_analysis = false; + New_Session->ole_data_ptr = nullptr; + New_Session->ole_data_ptr = 0; return New_Session; } diff --git a/src/decompress/file_decomp.h b/src/decompress/file_decomp.h index 055538930..f5a8cda10 100644 --- a/src/decompress/file_decomp.h +++ b/src/decompress/file_decomp.h @@ -27,6 +27,9 @@ #include "main/snort_types.h" +#define NEW_OFFICE_FRMT 120 +#define OLD_OFFICE_FRMT 27 + /* Function return codes used internally and with caller */ // FIXIT-L these need to be split into internal-only codes and things that may be returned to the // application. The codes used by PDF and SWF should be standardized. PDF is returning BlockIn and @@ -61,10 +64,11 @@ enum file_compression_type_t #define FILE_SWF_ZLIB_BIT (0x00000002) #define FILE_PDF_DEFL_BIT (0x00000004) #define FILE_ZIP_DEFL_BIT (0x00000008) +#define FILE_VBA_EXTR_BIT (0x00000010) #define FILE_PDF_ANY (FILE_PDF_DEFL_BIT) #define FILE_SWF_ANY (FILE_SWF_LZMA_BIT | FILE_SWF_ZLIB_BIT) -#define FILE_ZIP_ANY (FILE_ZIP_DEFL_BIT) +#define FILE_ZIP_ANY (FILE_ZIP_DEFL_BIT | FILE_VBA_EXTR_BIT) /* Error codes either passed to caller via the session->Error_Alert of the File_Decomp_Alert() call-back function. */ @@ -136,6 +140,9 @@ struct fd_session_t uint8_t Decomp_Type; // Active decompression type uint8_t Sig_State; // Sig search state machine uint8_t State; // main state machine + uint8_t* ole_data_ptr; // compressed ole file. + uint32_t ole_data_len; + bool vba_analysis; }; /* Macros */ diff --git a/src/decompress/file_decomp_zip.cc b/src/decompress/file_decomp_zip.cc index 3905a4849..2f929056d 100644 --- a/src/decompress/file_decomp_zip.cc +++ b/src/decompress/file_decomp_zip.cc @@ -22,6 +22,7 @@ #endif #include "file_decomp_zip.h" +#include "file_olefile.h" #include "helpers/boyer_moore_search.h" #include "utils/util.h" @@ -120,6 +121,12 @@ fd_status_t File_Decomp_End_ZIP(fd_session_t* SessionPtr) if ( SessionPtr->ZIP->State == ZIP_STATE_INFLATE ) Inflate_End(SessionPtr); + if (SessionPtr->ZIP->file_name) + { + delete[] SessionPtr->ZIP->file_name; + SessionPtr->ZIP->file_name = nullptr; + } + if ( SessionPtr->ZIP->header_searcher ) { delete SessionPtr->ZIP->header_searcher; @@ -169,6 +176,12 @@ fd_status_t File_Decomp_ZIP(fd_session_t* SessionPtr) parser->filename_length = 0; parser->extra_length = 0; + if (parser->file_name) + { + delete[] parser->file_name; + parser->file_name = nullptr; + } + // reset decompression progress parser->progress = 0; @@ -266,7 +279,7 @@ fd_status_t File_Decomp_ZIP(fd_session_t* SessionPtr) byte = *SessionPtr->Next_In; parser->filename_length |= byte << (parser->Index * 8); break; - // extra length + //extra length case ZIP_STATE_EXTRALEN: // check if we are done with the extra length if ( parser->Index == parser->Length ) @@ -274,15 +287,51 @@ fd_status_t File_Decomp_ZIP(fd_session_t* SessionPtr) // read the extra length, reset the index parser->Index = 0; - // skip the filename and extra fields + // read the filename next + if (parser->file_name == nullptr and parser->filename_length > 0) + parser->file_name = new char[parser->filename_length + 1]; + + parser->State = ZIP_STATE_FILENAME; + parser->Length = parser->filename_length; + continue; + } + // read the extra length + byte = *SessionPtr->Next_In; + parser->extra_length |= byte << (parser->Index * 8); + break; + + // filename + case ZIP_STATE_FILENAME: + // check if we are done with filename + if ( parser->Index == parser->Length ) + { + // read the extra length, reset the index + parser->Index = 0; + if (parser->file_name) + { + parser->file_name[parser->filename_length] = '\0'; + } + + //skip the extra fields parser->State = ZIP_STATE_SKIP; - parser->Length = parser->filename_length + parser->extra_length; + parser->Length = parser->extra_length; if ( (SessionPtr->Avail_Out > 0) && (parser->method == 8) ) { // we have available output space and // the compression type is deflate (8), // land on the compressed stream, init zlib + //If the filename ends with vbaProject.bin, then + //the file has vbaMacros. + + if (( parser->filename_length >= MACRO_BINNAME_LEN )and (parser->file_name)and (strcmp( + parser->file_name + parser->filename_length - MACRO_BINNAME_LEN, + macro_binname)==0)) + { + parser->Next = ZIP_STATE_OLE_FILE; + parser->Next_Length = 0; + continue; + } parser->Next = ZIP_STATE_INFLATE_INIT; parser->Next_Length = 0; continue; @@ -305,10 +354,16 @@ fd_status_t File_Decomp_ZIP(fd_session_t* SessionPtr) parser->Next_Length = 4; continue; } - // read the extra length - byte = *SessionPtr->Next_In; - parser->extra_length |= byte << (parser->Index * 8); + + //read the filename + if (parser->file_name && parser->Index < parser->filename_length) + parser->file_name[parser->Index] = (char)*SessionPtr->Next_In; break; + + case ZIP_STATE_OLE_FILE: + if (SessionPtr->vba_analysis) + SessionPtr->ole_data_ptr = SessionPtr->Next_Out; + //fallthrough // initialize zlib inflate case ZIP_STATE_INFLATE_INIT: parser->State = ZIP_STATE_INFLATE; @@ -316,7 +371,7 @@ fd_status_t File_Decomp_ZIP(fd_session_t* SessionPtr) if ( Inflate_Init(SessionPtr) == File_Decomp_Error ) return File_Decomp_Error; - // fallthrough + // fallthrough // perform zlib inflate case ZIP_STATE_INFLATE: { @@ -331,6 +386,9 @@ fd_status_t File_Decomp_ZIP(fd_session_t* SessionPtr) return File_Decomp_Error; } + if (SessionPtr->ole_data_ptr) + SessionPtr->ole_data_len = SessionPtr->Next_Out - SessionPtr->ole_data_ptr; + if ( status == File_Decomp_BlockOut ) { // ran out of output space @@ -435,7 +493,7 @@ fd_status_t File_Decomp_ZIP(fd_session_t* SessionPtr) parser->Index += SessionPtr->Avail_In; uint32_t min = SessionPtr->Avail_In < SessionPtr->Avail_Out ? - SessionPtr->Avail_In : SessionPtr->Avail_Out; + SessionPtr->Avail_In : SessionPtr->Avail_Out; // copy what we can Move_N(SessionPtr, min); @@ -495,3 +553,4 @@ fd_status_t File_Decomp_ZIP(fd_session_t* SessionPtr) return File_Decomp_BlockIn; } + diff --git a/src/decompress/file_decomp_zip.h b/src/decompress/file_decomp_zip.h index c79a52cd2..9a950bf6d 100644 --- a/src/decompress/file_decomp_zip.h +++ b/src/decompress/file_decomp_zip.h @@ -29,6 +29,10 @@ namespace snort class BoyerMooreSearchCase; } +#define MACRO_BINNAME_LEN 14 + +static const char* const macro_binname = "vbaProject.bin"; + static const uint32_t ZIP_LOCAL_HEADER = 0x04034B50; static const uint8_t header_pattern[4] = { 0x50, 0x4B, 0x03, 0x04 }; static const uint8_t DATA_DESC_BIT = 0x08; @@ -56,11 +60,13 @@ enum fd_ZIP_states ZIP_STATE_FILENAMELEN, // filename length (2 bytes) ZIP_STATE_EXTRALEN, // extra field length (2 bytes) - // skipped: - // ZIP_STATE_FILENAME, // filename field (filenamelen bytes) + ZIP_STATE_FILENAME, // filename field (filenamelen bytes) + + //skipped: // ZIP_STATE_EXTRA, // extra field (extralen bytes) // ZIP_STATE_STREAM, // compressed stream (compsize bytes) + ZIP_STATE_OLE_FILE, ZIP_STATE_INFLATE_INIT, // initialize zlib inflate ZIP_STATE_INFLATE, // perform zlib inflate ZIP_STATE_SEARCH, // search for local header @@ -83,7 +89,7 @@ struct fd_ZIP_t uint32_t compressed_size; uint16_t filename_length; uint16_t extra_length; - + char* file_name; // field index uint32_t Index; @@ -109,3 +115,4 @@ fd_status_t File_Decomp_End_ZIP(fd_session_t*); fd_status_t File_Decomp_ZIP(fd_session_t*); #endif + diff --git a/src/decompress/file_olefile.cc b/src/decompress/file_olefile.cc new file mode 100644 index 000000000..d2ad7a106 --- /dev/null +++ b/src/decompress/file_olefile.cc @@ -0,0 +1,684 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// file_olefile.cc author Vigneshwari Viswanathan vignvisw@cisco.com + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "file_olefile.h" + +DirectoryList :: ~DirectoryList() +{ + std::unordered_map::iterator it = oleentry.begin(); + + while (it != oleentry.end()) + { + FileProperty* node = it->second; + delete[] node->get_name(); + delete node; + it = oleentry.erase(it); + } +} + +// The function walk_directory_list() will read the entries of all the directory entry +// arrays of an ole file and will create a mapping between the storage/stream name and +// the fileproperty object. Each entry of directory entry array will be of 64 bytes. +// +// The first directory entry array value is obtained from the ole header. The subsequent +// sectors will be obtained by referring the fat list array. +// +// Each object of fileproperty will give us the information about the starting sector of +// that storage/stream, overall size of the stream/storage and other metadata. + +// The content of any storage/stream is read by combining all the sectors of that stream/ +// storage and it will begin with starting sector value mentioned in fileproperty object. +// Also, this starting sector value can be used to obtain the next sector to read by +// referring the FAT list array. +void OleFile :: walk_directory_list() +{ + int32_t current_sector; + uint16_t sector_size; + uint8_t* name_buf; + int bytes_copied; + FileProperty* node; + char* file_name; + + current_sector = header->get_first_dir(); + sector_size = header->get_sector_size(); + + dir_list = new DirectoryList(); + + while (current_sector > INVALID_SECTOR) + { + const uint8_t* buf = file_buf; + uint32_t start_offset = get_fat_offset(current_sector); + + if ((start_offset + sector_size) > buf_len) + return; + + buf += start_offset; + + int32_t count = 0; + + while (count < (sector_size/DIR_ENTRY_SIZE)) + { + node = new FileProperty; + name_buf = new uint8_t[32]; + + // The filename is UTF16 encoded and will be of the size 64 bytes. + dir_list->utf_state = new snort::UtfDecodeSession(); + if (!header->get_byte_order()) + dir_list->utf_state->set_decode_utf_state_charset(CHARSET_UTF16LE); + else + dir_list->utf_state->set_decode_utf_state_charset(CHARSET_UTF16BE); + dir_list->utf_state->decode_utf(buf, OLE_MAX_FILENAME_LEN_UTF16, name_buf, + OLE_MAX_FILENAME_ASCII, &bytes_copied); + + node->set_name(name_buf); + + node->set_file_type(buf + DIR_FILE_TYPE_OFFSET); + + node->set_color(buf + DIR_COLOR_OFFSET); + + node->set_lef_sib_id(buf + DIR_LEFT_SIB_OFFSET, header->get_byte_order()); + + node->set_rig_sib_id(buf + DIR_RIGHT_SIB_OFFSET, header->get_byte_order()); + + node->set_root_node_id(buf + DIR_ROOT_NODE_OFFSET, header->get_byte_order()); + + node->set_cls_id(buf + DIR_CLS_ID_OFFSET); + + node->set_starting_sector(buf + DIR_STARTING_SEC_OFFSET, header->get_byte_order()); + + node->set_stream_size(buf + DIR_STREAM_SIZE_OFFSET, header->get_byte_order()); + + buf += DIR_NEXT_ENTR_OFFSET; + + //Insert the oleentry + file_name = (char*)name_buf; + + if (strcmp(file_name, ROOT_ENTRY) == 0) + dir_list->set_mini_stream_sector(node->get_starting_sector()); + object_type type = node->get_file_type(); + // check for all the empty/non valid entries in the directory list. + if (!(type == ROOT_STORAGE or type == STORAGE or type == STREAM)) + { + delete node; + delete[] name_buf; + } + else + dir_list->oleentry.insert({ file_name, node }); + count++; + delete dir_list->utf_state; + } + // Reading the next sector of current_sector by referring the FAT list array. + // A negative number suggests the end of directory entry array and there are + // no more stream/storage to read. + int32_t next_sector = get_next_fat_sector(current_sector); + if (next_sector > INVALID_SECTOR) + current_sector = next_sector; + else + current_sector = INVALID_SECTOR; + } +} + +FileProperty* DirectoryList :: get_file_node(char* name) +{ + std::unordered_map::iterator it; + + it = oleentry.find(name); + + if (it != oleentry.end()) + return(it->second); + return nullptr; +} + +// Every index of fat_list array is the fat sector ID and the value present +// at that index will be its corresponding next fat sector ID. +int32_t OleFile :: get_next_fat_sector(int32_t sec_id) +{ + if (fat_list and sec_id > INVALID_SECTOR and sec_id < fat_list_len) + return fat_list[sec_id]; + else + return INVALID_SECTOR; +} + +// Every index of mini_fat_list array is the minifat sector ID and the value present +// at that index will be its corresponding next minifat sector ID. +int32_t OleFile :: get_next_mini_fat_sector(int32_t sec_id) +{ + if (mini_fat_list and sec_id > INVALID_SECTOR and sec_id < mini_fat_list_len) + return mini_fat_list[sec_id]; + else + return INVALID_SECTOR; +} + +// The offset of a sector is header_size + (sector_number * size_of_each_sector). +int32_t OleFile :: get_fat_offset(int32_t sec_id) +{ + int32_t byte_offset; + byte_offset = OLE_HEADER_LEN + (sec_id * header->get_sector_size()); + return(byte_offset); +} + +// Example to get the mini fat sector offset. +// If, +// sector size = 512 bytes +// mini sector size = 64 bytes +// and sector 2 and 5 are storing the mini fat sectors (assuming the mini fat sector is +// starting with sector 2) , then the offset of 12th mini fat sector is calculated as +// below: +// +// mini fat sector per sector = 512 bytes / 64 bytes = 8. +// The first 8 mini fat sectors would be stored in the fat sector 2 and therefore the +// 12th minifat sector would be stored in the next fat sector of sector 2 which is sector +// 5.( we'll get this info from the fat sector array ( get_next_fat_sector() ) where +// sector 5 would be mapped to sector 2 as the next fat sector. -2 will be mapped against +// sector 5, as sector 5 is the last sector storing mini fat sectors ) +// +// The 12th mini fat sector would be the 4th ( index = 3) mini fat sector stored in fat sector 5. +// +// The offset of sector 5 = header_size + (sector_size) * 5 = 512 + 512 * 5 = 3072 bytes. +// +// The offset of 12th mini fat sector = (offset of 5th fat sector ) + ( offset of 4th(index is 3) +// mini fat sector in 5th fat sector) +// = 3072 + 64 * 3 +// = 3264 bytes. +int32_t OleFile :: get_mini_fat_offset(int32_t sec_id) +{ + int32_t sec_position, mini_sec_position, count, current_sector; + int32_t byte_offset, mini_fat_persector; + + mini_fat_persector = header->get_sector_size() / header->get_mini_sector_size(); + + if (sec_id >= mini_fat_persector) + { + sec_position = sec_id/mini_fat_persector; + mini_sec_position = sec_id % mini_fat_persector; + } + else + { + sec_position = 0; + mini_sec_position = sec_id; + } + + count = 0; + + current_sector = dir_list->get_mini_stream_sector(); + + while (count < sec_position) + { + int32_t next_sector = get_next_fat_sector(current_sector); + if (next_sector <= INVALID_SECTOR) + return -1; + count++; + current_sector = next_sector; + } + byte_offset = OLE_HEADER_LEN + (current_sector * header->get_sector_size()) + + (mini_sec_position * + header->get_mini_sector_size()); + return byte_offset; +} + +void OleFile :: get_file_data(char* file, uint8_t*& file_data, int32_t& data_len) +{ + FileProperty* node; + uint16_t sector_size,mini_sector_size; + node = dir_list->get_file_node(file); + data_len = 0; + + sector_size = header->get_sector_size(); + mini_sector_size = header->get_mini_sector_size(); + if (node) + { + int32_t starting_sector; + int64_t stream_size; + sector_type is_fat = FAT_SECTOR; + uint32_t byte_offset, bytes_to_copy; + uint8_t* temp_data; + + starting_sector = node->get_starting_sector(); + stream_size = node->get_stream_size(); + + file_data = new uint8_t[stream_size]; + temp_data = file_data; + if (stream_size <= header->get_minifat_cutoff()) + is_fat = MINIFAT_SECTOR; + + if (is_fat == FAT_SECTOR) + { + int32_t current_sector; + current_sector = starting_sector; + while (current_sector > INVALID_SECTOR) + { + byte_offset = get_fat_offset(current_sector); + if (byte_offset > buf_len) + return; + + if ((byte_offset + sector_size)> buf_len) + { + memcpy(temp_data, (file_buf + byte_offset), (buf_len - byte_offset)); + data_len += buf_len - byte_offset; + return; + } + if ((data_len + sector_size) < stream_size) + bytes_to_copy = sector_size; + else + bytes_to_copy = stream_size - data_len; + + memcpy(temp_data, (file_buf + byte_offset), bytes_to_copy); + temp_data += sector_size; + data_len += bytes_to_copy; + + int32_t next_sector = get_next_fat_sector(current_sector); + current_sector = next_sector; + } + } + else + { + int32_t mini_sector; + mini_sector = node->get_starting_sector(); + while (mini_sector > INVALID_SECTOR) + { + byte_offset = get_mini_fat_offset(mini_sector); + if (byte_offset > buf_len) + return; + + if ((byte_offset + mini_sector_size) > buf_len) + { + memcpy(temp_data, (file_buf + byte_offset), (buf_len - byte_offset)); + data_len += buf_len - byte_offset; + return; + } + if ((data_len + mini_sector_size) < stream_size) + bytes_to_copy = mini_sector_size; + else + bytes_to_copy = stream_size - data_len; + + memcpy(temp_data, (file_buf + byte_offset), bytes_to_copy); + temp_data += mini_sector_size; + data_len += bytes_to_copy; + + int32_t next_sector = get_next_mini_fat_sector(mini_sector); + mini_sector = next_sector; + } + } + } +} + +// The function populate_fat_list() reads the contents of FAT array sectors to create +// the the fat_list array where each of the indices represents the current sector +// ID and the value at that index would be its next sector ID. +void OleFile :: populate_fat_list() +{ + int32_t current_sector, fat_sector_curr_cnt = 0; + int32_t fat_sector = header->get_difat_array(fat_sector_curr_cnt); + int32_t max_secchain_cnt = header->get_sector_size()/4; + int32_t count = 0; + + fat_list_len = ( header->get_fat_sector_count() * header->get_sector_size() ) / 4; + if (fat_list_len < 1) + return; + + fat_list = new int32_t[fat_list_len]; + + memset(fat_list, -1, fat_list_len); + + current_sector = fat_sector; + while (current_sector > INVALID_SECTOR) + { + uint32_t byte_offset = OLE_HEADER_LEN + (current_sector * header->get_sector_size()); + + const uint8_t* buf = file_buf; + + buf += byte_offset; + + if ((byte_offset + header->get_sector_size()) > buf_len) + return; + + while ((count - (fat_sector_curr_cnt * max_secchain_cnt)) < (max_secchain_cnt)) + { + if (!header->get_byte_order()) + fat_list[count] = LETOHL_UNALIGNED(buf); + else + fat_list[count] = BETOHL_UNALIGNED(buf); + count++; + buf += 4; + } + fat_sector_curr_cnt++; + if (fat_sector_curr_cnt < MAX_DIFAT_SECTORS) + current_sector = header->get_difat_array(fat_sector_curr_cnt); + else + return; + } +} + +// The function populate_mini_fat_list() reads the contents of mini FAT array sectors to +// create the the mini_fat_list array where each of the indices represents the +// current mini sector ID and the value at that index would be its next mini +// sector ID. +void OleFile :: populate_mini_fat_list() +{ + int32_t minifat_sector = header->get_first_minifat(), current_sector; + + int32_t max_secchain_cnt = header->get_sector_size()/4; + int32_t count = 0; + + mini_fat_list_len = ( header->get_minifat_count() * header->get_sector_size() ) / 4; + if (mini_fat_list_len < 1) + return; + + mini_fat_list = new int32_t[mini_fat_list_len]; + + memset(mini_fat_list, -1, mini_fat_list_len); + + current_sector = minifat_sector; + int32_t minfat_curr_cnt = 0; + while (current_sector > INVALID_SECTOR) + { + uint32_t byte_offset = OLE_HEADER_LEN + (current_sector * header->get_sector_size()); + + if ((byte_offset + header->get_sector_size()) > buf_len) + return; + + const uint8_t* buf = file_buf; + + buf += byte_offset; + + while ((count - (minfat_curr_cnt * max_secchain_cnt)) < max_secchain_cnt) + { + if (!header->get_byte_order()) + mini_fat_list[count] = LETOHL_UNALIGNED(buf); + else + mini_fat_list[count] = BETOHL_UNALIGNED(buf); + count++; + buf += 4; + } + minfat_curr_cnt++; + int32_t next_sector = get_next_fat_sector(current_sector); + if (next_sector > INVALID_SECTOR) + current_sector = next_sector; + else + current_sector = INVALID_SECTOR; + } +} + +// API to parse the OLE File Header. +// The header is always located at the beginning of the file, +// and its size is exactly 512 bytes. This implies that the first +// sector (with SecID 0) always starts at file offset 512. +bool OleFile :: parse_ole_header() +{ + header = new OleHeader; + if (!header->set_byte_order(file_buf + HEADER_BYTE_ORDER_OFFSET)) + return false; + + // Header Signature (8 bytes) is Identification signature of the OLE file, + // and must be of the value 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1. + if (!header->match_ole_sig(file_buf)) + return false; + + // Minor Version field should be set to 0x003E. + header->set_minor_version(file_buf + HEADER_MINOR_VER_OFFSET); + + // Major Version field is set to either 0x0003 (version 3) or 0x0004 (version 4). + header->set_major_version(file_buf + HEADER_MAJOR_VER_OFFSET); + + // This field specifies the sector size of the compound file as a power of 2. + header->set_sector_size(file_buf + HEADER_SECTR_SIZE_OFFSET); + + // This field specifies the sector size of the Mini Stream as a power of 2. + header->set_mini_sector_size(file_buf + HEADER_MIN_SECTR_SIZE_OFFSET); + + header->set_fat_sector_count(file_buf + HEADER_FAT_SECTR_CNT_OFFSET); + + header->set_first_dir(file_buf + HEADER_FIRST_DIR_SECTR_OFFSET); + + header->set_minifat_cutoff(file_buf + HEADER_MINFAT_CUTOFF_OFFSET); + + header->set_first_minifat(file_buf + HEADER_FIRST_MINFAT_OFFSET); + + header->set_minifat_count(file_buf + HEADER_MINFAT_COUNT_OFFSET); + + header->set_first_difat(file_buf + HEADER_FIRST_DIFAT_OFFSET); + + header->set_difat_count(file_buf + HEADER_DIFAT_CNT_OFFSET); + + header->set_dir_sector_count(file_buf + HEADER_DIR_SECTR_CNT_OFFSET); + + // DIFAT array of 32-bit integer fields contains the first 109 FAT sector locations of the compound file. + header->set_difat_array(file_buf + HEADER_DIFAT_ARRY_OFFSET); + + return true; +} + +// The vba code in a VBA macro file begins with the keyword "ATTRIBUT" .This +// keyword is used to calculate the offset of vba code and is decompressed using +// RLE algorithm. +int32_t OleFile :: get_file_offset(const uint8_t* data, int32_t data_len) +{ + search_handle = snort::LiteralSearch::setup(); + searcher = snort::LiteralSearch::instantiate(search_handle, + (const uint8_t*)"ATTRIBUT", 8, true); + if (searcher == nullptr) + return -1; + + int32_t offset = searcher->search(search_handle, data, data_len); + delete searcher; + snort::LiteralSearch::cleanup(search_handle); + return offset; +} + +int32_t cli_readn(const uint8_t*& fd, int32_t& data_len, void* buff, int32_t count) +{ + int32_t i; + + for (i = 0; i < count; i++) + { + if (data_len) + { + *((uint8_t*)buff + i) = *(fd + i); + data_len -= 1; + } + else + { + break; + } + } + + fd += i; + return i; +} + +// Function for RLE decompression. +// +// Run-length encoding (RLE) is a very simple form of data compression +// in which a stream of data is given as the input (i.e. "AAABBCCCC") and +// the output is a sequence of counts of consecutive data values in a row +// (i.e. "3A2B4C"). This type of data compression is lossless, meaning that +// when decompressed, all of the original data will be recovered when decoded. +void OleFile :: decompression(const uint8_t* data, int32_t* data_len, uint8_t*& local_vba_buffer, + uint32_t* vba_buffer_offset) +{ + int16_t header; + bool flagCompressed; + unsigned char buffer[VBA_COMPRESSION_WINDOW]={ }; + uint16_t token; + unsigned int pos, shift, mask, distance; + uint8_t flag; + bool clean; + + if (!data) + return; + + if (*data!= SIG_COMP_CONTAINER) + return; + + header = LETOHS_UNALIGNED(data + 1); + + flagCompressed = header & 0x8000; + + if (((header >> 12) & 0x07) != 0b011) + { + } + + data += 3; + *data_len -= 3; + + if (flagCompressed == 0) + { + memcpy(&buffer, data, *data_len); + return; + } + + pos = 0; + clean = 1; + int32_t size = *data_len; + while (cli_readn(data, size, &flag, 1)) + { + for (mask = 1; mask < 0x100; mask <<= 1) + { + unsigned int winpos = pos % VBA_COMPRESSION_WINDOW; + if (flag & mask) + { + uint16_t len; + uint32_t srcpos; + + if (!cli_readn(data, size, &token, 2)) + return; + + shift = 12 - (winpos > 0x10) - (winpos > 0x20) - (winpos > 0x40) - (winpos > + 0x80) - (winpos > 0x100) - (winpos > 0x200) - (winpos > 0x400) - (winpos > + 0x800); + len = (uint16_t)((token & ((1 << shift) - 1)) + 3); + distance = token >> shift; + + srcpos = pos - distance - 1; + if ((((srcpos + len) % VBA_COMPRESSION_WINDOW) < winpos)and + ((winpos + len) < VBA_COMPRESSION_WINDOW) and + (((srcpos % VBA_COMPRESSION_WINDOW) + len) < VBA_COMPRESSION_WINDOW) and + (len <= VBA_COMPRESSION_WINDOW)) + { + srcpos %= VBA_COMPRESSION_WINDOW; + memcpy(&buffer[winpos], &buffer[srcpos], + len); + pos += len; + } + else + while (len-- > 0) + { + srcpos = (pos - distance - 1) % VBA_COMPRESSION_WINDOW; + buffer[pos++ % VBA_COMPRESSION_WINDOW] = buffer[srcpos]; + } + } + else + { + if ((pos != 0)and (winpos == 0) and clean) + { + if (cli_readn(data, size, &token, 2) != 2) + { + return; + } + clean = 0; + break; + } + if (cli_readn(data, size, &buffer[winpos], 1) == 1) + pos++; + } + clean = 1; + } + } + + int32_t decomp_len = strlen((char*)buffer); + + if ((*vba_buffer_offset + decomp_len) > MAX_VBA_BUFFER_LEN) + { + decomp_len = MAX_VBA_BUFFER_LEN - *vba_buffer_offset; + } + memcpy((local_vba_buffer + *vba_buffer_offset), buffer, decomp_len); + *vba_buffer_offset += decomp_len; +} + +// Function to extract the VBA data and send it for RLE decompression. +void OleFile :: find_and_extract_vba(uint8_t*& vba_buf, uint32_t& vba_buf_len) +{ + std::unordered_map::iterator it = dir_list->oleentry.begin(); + uint32_t vba_buffer_offset = 0; + vba_buf = new uint8_t[MAX_VBA_BUFFER_LEN + 1](); + + while (it != dir_list->oleentry.end()) + { + FileProperty* node; + uint8_t* data = nullptr; + int32_t data_len; + node = it->second; + ++it; + if (node->get_file_type() == STREAM) + { + get_file_data(node->get_name(), data, data_len); + uint8_t* data1 = data; + int32_t offset = get_file_offset(data, data_len); + if (offset <= 0) + { + delete[] data1; + continue; + } + + data += offset - 4; + data_len = data_len - offset + 4; + decompression(data, &data_len, vba_buf, &vba_buffer_offset); + delete[] data1; + if ( vba_buffer_offset >= MAX_VBA_BUFFER_LEN) + break; + } + } + vba_buf_len = vba_buffer_offset; +} + +// Beginning function of ole file processing. +// +// An OLE file contains streams of data that look like files embedded within the +// OLE file.It can also contain storages, which is a folder that contains streams +// or other storages. +// +// Ole file processing begins with OLE header matching, followed by populating +// the FAT array-list which contains the mapping between current fat sector and +// its next fat sector.Followed by populating the mini-FAT array-list which +// contains the mapping between current mini-fat sector and its next mini-fat +// sector. Followed by reading the entries of all the directory entry arrays of +// an ole file and creating a mapping between the storage/stream name and the +// fileproperty object.Afterwards, based on the directory the data is fetched and +// extracted & RLE decompression is done. +void oleprocess(const uint8_t* const ole_file, const uint32_t ole_length, uint8_t*& vba_buf, uint32_t& vba_buf_len) +{ + if (ole_length < OLE_HEADER_LEN) + return; + + std::unique_ptr olefile (new OleFile(ole_file,ole_length)); + + if (!olefile->parse_ole_header()) + return; + + olefile->populate_fat_list(); + olefile->populate_mini_fat_list(); + olefile->walk_directory_list(); + olefile->find_and_extract_vba(vba_buf, vba_buf_len); +} + diff --git a/src/decompress/file_olefile.h b/src/decompress/file_olefile.h new file mode 100644 index 000000000..05179ee64 --- /dev/null +++ b/src/decompress/file_olefile.h @@ -0,0 +1,281 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// file_olefile.h author Vigneshwari Viswanathan vignvisw@cisco.com + +#ifndef FILE_OLE_H +#define FILE_OLE_H + +#include "file_oleheader.h" + +#include +#include + +#include "helpers/literal_search.h" +#include "utils/util.h" +#include "utils/util_utf.h" + +#define OLE_MAX_FILENAME_LEN_UTF16 64 +#define OLE_MAX_FILENAME_ASCII 32 + +#define OLE_HEADER_LEN 512 +#define DIR_ENTRY_SIZE 128 +#define ROOT_ENTRY "Root Entry" + +#define SIG_COMP_CONTAINER 0x01 +#define VBA_COMPRESSION_WINDOW 4096 +#define MAX_VBA_BUFFER_LEN 16384 + +#define INVALID_SECTOR -1 + +#define DIR_FILE_TYPE_OFFSET 66 +#define DIR_COLOR_OFFSET 67 +#define DIR_LEFT_SIB_OFFSET 68 +#define DIR_RIGHT_SIB_OFFSET 72 +#define DIR_ROOT_NODE_OFFSET 76 +#define DIR_CLS_ID_OFFSET 80 +#define DIR_STARTING_SEC_OFFSET 116 +#define DIR_STREAM_SIZE_OFFSET 120 +#define DIR_NEXT_ENTR_OFFSET 128 + +#define memcpy_id(destn, dsize, src, ssize) \ + ((dsize>=ssize) ? memcpy(destn, src, ssize) : memcpy( \ + destn, src, dsize)) + +enum object_type +{ + EMPTY = 0x00, + STORAGE = 0x01, + STREAM = 0x02, + ROOT_STORAGE = 0x05 +}; + +enum color_flag +{ + RED = 0x00, + BLACK = 0x01 +}; + +enum sector_type +{ + FAT_SECTOR = 0, + MINIFAT_SECTOR = 1 +}; + +int32_t cli_readn(const uint8_t*& fd, int32_t& data_len, void* buff, int32_t count); + +struct FileProperty +{ +public: + void set_name(uint8_t* f_name) + { + name = (char*)f_name; + } + + char* get_name() + { + return name; + } + + void set_file_type(const uint8_t* buf) + { + file_type = (object_type)*buf; + } + + object_type get_file_type() + { + return file_type; + } + + void set_color(const uint8_t* buf) + { + color = (color_flag)*buf; + } + + color_flag get_color() + { + return color; + } + + void set_lef_sib_id(const uint8_t* buf, byte_order_endianess endian) + { + lef_sib_id = (!endian) ? LETOHL_UNALIGNED(buf) : BETOHL_UNALIGNED(buf); + } + + int32_t get_lef_sib_id() + { + return lef_sib_id; + } + + void set_rig_sib_id(const uint8_t* buf, byte_order_endianess endian) + { + rig_sib_id = (!endian) ? LETOHL_UNALIGNED(buf) : BETOHL_UNALIGNED(buf); + } + + int32_t get_rig_sib_id() + { + return rig_sib_id; + } + + void set_root_node_id(const uint8_t* buf, byte_order_endianess endian) + { + root_node_id = (!endian) ? LETOHL_UNALIGNED(buf) : BETOHL_UNALIGNED(buf); + } + + int32_t get_root_node_id() + { + return root_node_id; + } + + void set_cls_id(const uint8_t* buf) + { + memcpy_id(cls_id, sizeof(cls_id), buf, 16); + } + + char* get_cls_id() + { + return cls_id; + } + + void set_starting_sector(const uint8_t* buf, byte_order_endianess endian) + { + starting_sector = (!endian) ? LETOHL_UNALIGNED(buf) : BETOHL_UNALIGNED(buf); + } + + int32_t get_starting_sector() + { + return starting_sector; + } + + void set_stream_size(const uint8_t* buf, byte_order_endianess endian) + { + stream_size = (!endian) ? LETOHLL_UNALIGNED(buf) : BETOHLL_UNALIGNED(buf); + } + + int64_t get_stream_size() + { + return stream_size; + } + + FileProperty() + { + name = nullptr; + } + +private: + char* name; + object_type file_type; + color_flag color; + int32_t lef_sib_id; + int32_t rig_sib_id; + int32_t root_node_id; + char cls_id[16]; + int32_t starting_sector; + int64_t stream_size; +}; + +class DirectoryList +{ +public: + std::unordered_map oleentry; + snort::UtfDecodeSession* utf_state; + + bool is_file_exists(char* name); + FileProperty* get_file_node(char* name); + int32_t get_file_sector(char* name); + bool is_mini_sector(char* name); + void set_mini_stream_sector(int32_t mini_stream_sector) + { + this->mini_stream_sector = mini_stream_sector; + } + + int32_t get_mini_stream_sector() + { + return mini_stream_sector; + } + + DirectoryList() + { + utf_state = nullptr; + mini_stream_sector = -1; + } + + ~DirectoryList(); + +private: + int32_t mini_stream_sector; +}; + +class OleFile +{ +public: + bool parse_ole_header(); + void populate_fat_list(); + void populate_mini_fat_list(); + void walk_directory_list(); + void find_and_extract_vba(uint8_t*&, uint32_t&); + int32_t get_next_fat_sector(int32_t sec_id); + int32_t get_next_mini_fat_sector(int32_t sec_id); + int32_t get_fat_offset(int32_t sec_id); + int32_t get_mini_fat_offset(int32_t sec_id); + int32_t get_file_offset(const uint8_t*, int32_t data_len); + void get_file_data(char*, uint8_t*&, int32_t&); + + void decompression(const uint8_t* data, int32_t* data_len, uint8_t*& buffer, + uint32_t* buffer_ofset); + + int search_nocase(const uint8_t* buffer, unsigned buffer_len) const + { + return searcher->search(search_handle, buffer, buffer_len); + } + + OleFile(const uint8_t* file_buf, const uint32_t buf_len) + { + //header = new OleHeader; + this->file_buf = file_buf; + this->buf_len = buf_len; + //dir_list = new DirectoryList(); + } + + ~OleFile() + { + delete header; + delete dir_list; + delete[] fat_list; + delete[] mini_fat_list; + } + + snort::LiteralSearch* searcher = nullptr; + snort::LiteralSearch::Handle* search_handle = nullptr; + +private: + const uint8_t* file_buf; + uint32_t buf_len; + + OleHeader* header = nullptr; + DirectoryList* dir_list = nullptr; + + int32_t* fat_list = nullptr; + int32_t fat_list_len = 0; + int32_t* mini_fat_list = nullptr; + int32_t mini_fat_list_len = 0; +}; + +void oleprocess(const uint8_t* const, const uint32_t, uint8_t*&, uint32_t&); +#endif + diff --git a/src/decompress/file_oleheader.cc b/src/decompress/file_oleheader.cc new file mode 100644 index 000000000..fb72c0f2b --- /dev/null +++ b/src/decompress/file_oleheader.cc @@ -0,0 +1,189 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// file_oleheader.cc author Amarnath Nayak amarnaya@cisco.com + +#include "file_oleheader.h" + +unsigned char hdr_sig[8] = { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }; + +bool OleHeader::set_byte_order(const uint8_t* buf) +{ + byte_order = buf[0]<< 8 | buf[1]; + if (byte_order == 0XFEFF) + byte_order_endian = LITL_END; + + else if (byte_order == 0XFFFE) + byte_order_endian = BIG_END; + + else + return false; + + return true; +} + +byte_order_endianess OleHeader::get_byte_order() +{ + return byte_order_endian; +} + +bool OleHeader::match_ole_sig(const uint8_t* buf) +{ + memcpy(sig ,(const char*)buf, 8); + + if (memcmp(sig, hdr_sig, 8) == 0) + return true; + else + return false; +} + +void OleHeader::set_minor_version(const uint8_t* buf) +{ + minor_version = (!byte_order_endian) ? LETOHS_UNALIGNED(buf) : BETOHS_UNALIGNED(buf); +} + +uint16_t OleHeader::get_minor_version() +{ + return minor_version; +} + +void OleHeader::set_major_version(const uint8_t* buf) +{ + major_version = (!byte_order_endian) ? LETOHS_UNALIGNED(buf) : BETOHS_UNALIGNED(buf); +} + +uint16_t OleHeader::get_major_version() +{ + return major_version; +} + +void OleHeader::set_sector_size(const uint8_t* buf) +{ + sector_size = (!byte_order_endian) ? exp2(LETOHS_UNALIGNED(buf)) : exp2(BETOHS_UNALIGNED(buf)); +} + +uint16_t OleHeader::get_sector_size() +{ + return sector_size; +} + +void OleHeader::set_mini_sector_size(const uint8_t* buf) +{ + mini_sector_size = (!byte_order_endian) ? exp2(LETOHS_UNALIGNED(buf)) : exp2(BETOHS_UNALIGNED( + buf)); +} + +uint16_t OleHeader::get_mini_sector_size() +{ + return mini_sector_size; +} + +void OleHeader::set_dir_sector_count(const uint8_t* buf) +{ + dir_sector_count = (!byte_order_endian) ? LETOHL_UNALIGNED(buf) : BETOHL_UNALIGNED(buf); +} + +int32_t OleHeader::get_dir_sector_count() +{ + return dir_sector_count; +} + +void OleHeader::set_first_dir(const uint8_t* buf) +{ + first_dir = (!byte_order_endian) ? LETOHL_UNALIGNED(buf) : BETOHL_UNALIGNED(buf); +} + +int32_t OleHeader::get_first_dir() +{ + return first_dir; +} + +void OleHeader::set_difat_count(const uint8_t* buf) +{ + difat_count = (!byte_order_endian) ? LETOHL_UNALIGNED(buf) : BETOHL_UNALIGNED(buf); +} + +int32_t OleHeader::get_difat_count() +{ + return difat_count; +} + +void OleHeader::set_fat_sector_count(const uint8_t* buf) +{ + fat_sector_count = (!byte_order_endian) ? LETOHL_UNALIGNED(buf) : BETOHL_UNALIGNED(buf); +} + +int32_t OleHeader::get_fat_sector_count() +{ + return fat_sector_count; +} + +void OleHeader::set_minifat_cutoff(const uint8_t* buf) +{ + minifat_cutoff = (!byte_order_endian) ? LETOHL_UNALIGNED(buf) : BETOHL_UNALIGNED(buf); +} + +int32_t OleHeader::get_minifat_cutoff() +{ + return minifat_cutoff; +} + +void OleHeader::set_first_minifat(const uint8_t* buf) +{ + first_minifat = (!byte_order_endian) ? LETOHL_UNALIGNED(buf) : BETOHL_UNALIGNED(buf); +} + +int32_t OleHeader::get_first_minifat() +{ + return first_minifat; +} + +void OleHeader::set_minifat_count(const uint8_t* buf) +{ + minifat_count = (!byte_order_endian) ? LETOHL_UNALIGNED(buf) : BETOHL_UNALIGNED(buf); +} + +int32_t OleHeader::get_minifat_count() +{ + return minifat_count; +} + +void OleHeader::set_first_difat(const uint8_t* buf) +{ + first_difat = (!byte_order_endian) ? LETOHL_UNALIGNED(buf) : BETOHL_UNALIGNED(buf); +} + +int32_t OleHeader::get_first_difat() +{ + return first_difat; +} + +void OleHeader::set_difat_array(const uint8_t* buf) +{ + for (int i = 0; i < MAX_DIFAT_SECTORS; i++) + { + difat_array[i] = (!byte_order_endian) ? LETOHL_UNALIGNED(buf + (i * 4)) : + BETOHL_UNALIGNED(buf + (i * 4)); + } +} + +int32_t OleHeader::get_difat_array(int num) +{ + return difat_array[num]; +} + diff --git a/src/decompress/file_oleheader.h b/src/decompress/file_oleheader.h new file mode 100644 index 000000000..e32a2c160 --- /dev/null +++ b/src/decompress/file_oleheader.h @@ -0,0 +1,112 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// file_oleheader.h author Vigneshwari Viswanathan vignvisw@cisco.com + +#ifndef FILE_OLE_HEADER_H +#define FILE_OLE_HEADER_H + +#include +#include + +#include "main/snort_types.h" +#include "utils/endian.h" + +#define MAX_DIFAT_SECTORS 109 + +#define HEADER_MINOR_VER_OFFSET 24 +#define HEADER_MAJOR_VER_OFFSET 26 +#define HEADER_BYTE_ORDER_OFFSET 28 +#define HEADER_SECTR_SIZE_OFFSET 30 +#define HEADER_MIN_SECTR_SIZE_OFFSET 32 +#define HEADER_DIR_SECTR_CNT_OFFSET 40 +#define HEADER_FAT_SECTR_CNT_OFFSET 44 +#define HEADER_FIRST_DIR_SECTR_OFFSET 48 +#define HEADER_MINFAT_CUTOFF_OFFSET 56 +#define HEADER_FIRST_MINFAT_OFFSET 60 +#define HEADER_MINFAT_COUNT_OFFSET 64 +#define HEADER_FIRST_DIFAT_OFFSET 68 +#define HEADER_DIFAT_CNT_OFFSET 72 +#define HEADER_DIFAT_ARRY_OFFSET 76 + +enum byte_order_endianess +{ + LITL_END = 0, + BIG_END = 1 +}; + +class OleHeader +{ +public: + bool set_byte_order(const uint8_t* buf); + byte_order_endianess get_byte_order(); + bool match_ole_sig(const uint8_t* buf); + void set_minor_version(const uint8_t* buf); + uint16_t get_minor_version(); + void set_major_version(const uint8_t* buf); + uint16_t get_major_version(); + void set_sector_size(const uint8_t* buf); + uint16_t get_sector_size(); + void set_mini_sector_size(const uint8_t* buf); + uint16_t get_mini_sector_size(); + void set_dir_sector_count(const uint8_t* buf); + int32_t get_dir_sector_count(); + void set_first_dir(const uint8_t* buf); + int32_t get_first_dir(); + void set_difat_count(const uint8_t* buf); + int32_t get_difat_count(); + void set_fat_sector_count(const uint8_t* buf); + int32_t get_fat_sector_count(); + void set_minifat_cutoff(const uint8_t* buf); + int32_t get_minifat_cutoff(); + void set_first_minifat(const uint8_t* buf); + int32_t get_first_minifat(); + void set_minifat_count(const uint8_t* buf); + int32_t get_minifat_count(); + void set_first_difat(const uint8_t* buf); + int32_t get_first_difat(); + void set_difat_array(const uint8_t* buf); + int32_t get_difat_array(int num); + OleHeader() + { + first_dir = -1; + fat_sector_count = 0; + first_minifat = 0; + } + +private: + unsigned char sig[8]; //0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 + uint16_t minor_version; + uint16_t major_version; + uint16_t byte_order; + uint16_t sector_size; + uint16_t mini_sector_size; + int32_t dir_sector_count; + int32_t first_dir; + int32_t difat_count; + int32_t fat_sector_count; + int32_t minifat_cutoff; + int32_t first_minifat; + int32_t minifat_count; + int32_t first_difat; + int32_t difat_array[MAX_DIFAT_SECTORS]; + + byte_order_endianess byte_order_endian; +}; +#endif + diff --git a/src/decompress/test/CMakeLists.txt b/src/decompress/test/CMakeLists.txt new file mode 100644 index 000000000..a19d79824 --- /dev/null +++ b/src/decompress/test/CMakeLists.txt @@ -0,0 +1,9 @@ +add_cpputest( file_olefile_test + SOURCES ../file_olefile.cc + ../file_oleheader.cc +) + +add_cpputest(file_oleheader_test + SOURCES ../file_oleheader.cc +) + diff --git a/src/decompress/test/file_olefile_test.cc b/src/decompress/test/file_olefile_test.cc new file mode 100644 index 000000000..090d528cb --- /dev/null +++ b/src/decompress/test/file_olefile_test.cc @@ -0,0 +1,231 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +//file_olefile_test.cc author Amarnath Nayak + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "../file_olefile.h" +#include "../file_oleheader.h" + +#include "detection/detection_engine.h" +#include "helpers/literal_search.h" +#include "utils/util_utf.h" + +#include +#include +#include + +namespace snort +{ +LiteralSearch::Handle* LiteralSearch::setup() { return nullptr; } +void LiteralSearch::cleanup(LiteralSearch::Handle*) { } +LiteralSearch* LiteralSearch::instantiate(LiteralSearch::Handle*, const uint8_t*, unsigned, bool, + bool) { return nullptr; } +void UtfDecodeSession::set_decode_utf_state_charset(CharsetCode) { } +bool UtfDecodeSession::decode_utf(unsigned char const*, unsigned int, unsigned char*, unsigned int, + int*) { return true; } +UtfDecodeSession::UtfDecodeSession() { } +} + +TEST_GROUP(Olefile_oleprocess_test) +{ +}; + +TEST(Olefile_oleprocess_test, wrong_ole_sig) +{ + uint8_t ole_file[1000] = { 0 }; + ole_file[28] = 0XFE; + ole_file[29] = 0XFF; + uint32_t ole_length = 1000; + uint8_t* vba_buf = nullptr; + uint32_t vba_buf_len = 0; + oleprocess(ole_file, ole_length, vba_buf, vba_buf_len); +} + +TEST(Olefile_oleprocess_test, wrong_byte_order) +{ + uint8_t ole_file[1000] = { 0 }; + const uint32_t ole_length = 1000; + uint8_t* vba_buf = nullptr; + uint32_t vba_buf_len = 0; + oleprocess(ole_file, ole_length, vba_buf, vba_buf_len); +} + +TEST(Olefile_oleprocess_test, short_header_len) +{ + uint8_t ole_file[100] = { 0 }; + uint32_t ole_length = 100; + uint8_t* vba_buf = nullptr; + uint32_t vba_buf_len = 0; + oleprocess(ole_file, ole_length, vba_buf, vba_buf_len); +} + +TEST_GROUP(Olefile_ole) +{ +}; + +TEST(Olefile_ole, get_file_offset) +{ + OleFile* olefile = new OleFile(nullptr, 0); + int32_t res = olefile->get_file_offset(nullptr,0); + CHECK(res == -1); + delete olefile; +} + +TEST(Olefile_ole, decompression_empty_data) +{ + uint8_t* ole_data = nullptr; + uint8_t* buf = nullptr; + int32_t len = 0; + OleFile* olefile = new OleFile(nullptr, 0); + olefile->decompression(ole_data, &len, buf, nullptr); + CHECK(buf == nullptr); + delete olefile; +} + +TEST(Olefile_ole, decompression_invalid_data_1) +{ + uint8_t ole_data[10] = { 0 }; + uint8_t* buf = nullptr; + int32_t len = 10; + OleFile* olefile = new OleFile(nullptr, 0); + olefile->decompression(ole_data,&len, buf, nullptr); + CHECK(buf == nullptr); + delete olefile; +} + +TEST(Olefile_ole, decompression_invalid_chunk_header) +{ + uint8_t ole_data[10] ={ 0 }; + uint8_t* buf = nullptr; + int32_t len = 10; + ole_data[0] = 0x01; + OleFile* olefile = new OleFile(nullptr, 0); + olefile->decompression(ole_data,&len, buf, nullptr); + CHECK(buf == nullptr); + delete olefile; +} + +TEST(Olefile_ole, decompression_flag_0) +{ + uint8_t ole_data[10] ={ 0 }; + uint8_t* buf = nullptr; + int32_t len = 10; + ole_data[0] = 0x01; + OleFile* olefile = new OleFile(nullptr, 0); + olefile->decompression(ole_data,&len, buf, nullptr); + CHECK(buf == nullptr); + delete olefile; +} + +TEST(Olefile_ole, get_next_fat_sector_failure) +{ + OleFile* olefile = new OleFile(nullptr, 0); + int32_t res = olefile->get_next_fat_sector(20); + CHECK(res == -1); + delete olefile; +} + +TEST(Olefile_ole, get_next_mini_fat_sector_failure) +{ + OleFile* olefile = new OleFile(nullptr, 0); + int32_t res = olefile->get_next_mini_fat_sector(20); + CHECK(res == -1); + delete olefile; +} + +TEST(Olefile_ole, get_file_node_failure) +{ + char test[] = {'a','b',0}; + DirectoryList* dir_list = new DirectoryList(); + FileProperty* res = dir_list->get_file_node(test); + delete dir_list; + CHECK(res == nullptr); +} + +TEST_GROUP(fat_mini_fat_list) +{ +}; + +TEST(fat_mini_fat_list, fat_list_short_buf_len) +{ + uint8_t ole_buf[520] = { 0 }; + uint8_t ole_header_sig[8] = { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }; + memcpy(ole_buf, ole_header_sig, 8); + ole_buf[28] = 0xFF; + ole_buf[29] = 0xFE; + ole_buf[30] = 0x00; + ole_buf[31] = 0x09; + ole_buf[519] = 0xFE; + OleFile* olefile = new OleFile(ole_buf, 520); + olefile->parse_ole_header(); + olefile->populate_fat_list(); + CHECK(olefile->get_next_fat_sector(1) == -1); + delete olefile; +} + +TEST(fat_mini_fat_list, fat_list_read_r) +{ + uint8_t ole_buf[1025] = { 0 }; + uint8_t ole_header_sig[8] = { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }; + memcpy(ole_buf, ole_header_sig, 8); + ole_buf[28] = 0xFF; + ole_buf[29] = 0xFE; + ole_buf[30] = 0x00; + ole_buf[31] = 0x09; + ole_buf[47] = 0x01; + memset(ole_buf + 80, 0xFF, 435); + ole_buf[519] = 0x02; + OleFile* olefile = new OleFile(ole_buf, 1025); + olefile->parse_ole_header(); + olefile->populate_fat_list(); + CHECK(olefile->get_next_fat_sector(1) == 2); + delete olefile; +} + +TEST(fat_mini_fat_list, mini_fat_list_short_buf_len) +{ + uint8_t ole_buf[1550] = { 0 }; + uint8_t ole_header_sig[8] = { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }; + memcpy(ole_buf, ole_header_sig, 8); + ole_buf[28] = 0xFF; + ole_buf[29] = 0xFE; + ole_buf[30] = 0x00; + ole_buf[31] = 0x09; + ole_buf[32] = 0x00; + ole_buf[33] = 0x06; + ole_buf[61] = 0x03; + ole_buf[519] = 0xFE; + ole_buf[527] = 0xFE; + ole_buf[1539] = 0x01; + OleFile* olefile = new OleFile(ole_buf, 1550); + olefile->parse_ole_header(); + olefile->populate_fat_list(); + olefile->populate_mini_fat_list(); + CHECK(olefile->get_next_mini_fat_sector(1) == -1); + delete olefile; +} + +int main(int argc, char** argv) +{ + return CommandLineTestRunner::RunAllTests(argc, argv); +} + diff --git a/src/decompress/test/file_oleheader_test.cc b/src/decompress/test/file_oleheader_test.cc new file mode 100644 index 000000000..88c5f9ebf --- /dev/null +++ b/src/decompress/test/file_oleheader_test.cc @@ -0,0 +1,114 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2021 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// file_oleheader_test.cc author Amarnath Nayak + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "../file_oleheader.h" + +#include +#include +#include + +TEST_GROUP(OleHeader_big_endian_test) +{ + OleHeader header; + void setup() override + { + const uint8_t buf[2] = { 0xFF, 0xFE }; + header.set_byte_order(buf); + } +}; + +TEST(OleHeader_big_endian_test, set_byte_order) +{ + byte_order_endianess byte_order = header.get_byte_order(); + CHECK(byte_order == BIG_END); +} + +TEST(OleHeader_big_endian_test, set_minor_version) +{ + const uint8_t buf[2] = { 0x00, 0x3B }; + header.set_minor_version(buf); + uint16_t minor_version = header.get_minor_version(); + CHECK(minor_version == 0X003B); +} + +TEST(OleHeader_big_endian_test, set_major_version) +{ + const uint8_t buf[2] = { 0x00, 0x03 }; + header.set_major_version(buf); + uint16_t major_version = header.get_major_version(); + CHECK(major_version == 0x0003); +} + +TEST(OleHeader_big_endian_test, set_dir_sector_count) +{ + const uint8_t buf[4] = { 0x00, 0x00, 0x00, 0x01 }; + header.set_dir_sector_count(buf); + int32_t dir_sec_count = header.get_dir_sector_count(); + CHECK(dir_sec_count == 0x00000001); +} + +TEST(OleHeader_big_endian_test, set_difat_count) +{ + const uint8_t buf[4] = { 0x00, 0x00, 0x00, 0x01 }; + header.set_difat_count(buf); + int32_t difat_count = header.get_difat_count(); + CHECK(difat_count == 0x00000001); +} + +TEST(OleHeader_big_endian_test, set_fat_sector_count) +{ + const uint8_t buf[4] = { 0x00, 0x00, 0x00, 0x01 }; + header.set_fat_sector_count(buf); + int32_t fat_sector_count = header.get_fat_sector_count(); + CHECK(fat_sector_count == 0x00000001); +} + +TEST(OleHeader_big_endian_test, set_minifat_count) +{ + const uint8_t buf[4] = { 0x00, 0x00, 0x00, 0x01 }; + header.set_minifat_count(buf); + int32_t minfat_sector_count = header.get_minifat_count(); + CHECK(minfat_sector_count == 0x00000001); +} + +TEST(OleHeader_big_endian_test, set_first_difat) +{ + const uint8_t buf[4] = { 0x00, 0x00, 0x02, 0x00 }; + header.set_first_difat(buf); + int32_t first_difat = header.get_first_difat(); + CHECK(first_difat == 0x00000200); +} + +TEST(OleHeader_big_endian_test, BETOHLL_UNALIGNED) +{ + const uint8_t buf[8] = { 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04 }; + int64_t value = BETOHLL_UNALIGNED(buf); + CHECK(value == 0x0102030401020304); +} + +int main(int argc, char** argv) +{ + return CommandLineTestRunner::RunAllTests(argc, argv); +} + diff --git a/src/detection/fp_detect.cc b/src/detection/fp_detect.cc index 5b96e3a05..82ad63da5 100644 --- a/src/detection/fp_detect.cc +++ b/src/detection/fp_detect.cc @@ -957,6 +957,8 @@ static int fp_search(PortGroup* port_group, Packet* p, bool srvc) search_buffer( gadget, buf, buf.IBT_COOKIE, p, port_group, PM_TYPE_COOKIE, pc.cookie_searches); + + search_buffer(gadget, buf, buf.IBT_VBA, p, port_group, PM_TYPE_VBA, pc.vba_searches); } if ( MpseGroup* so = port_group->mpsegrp[PM_TYPE_SCRIPT] ) diff --git a/src/detection/fp_utils.cc b/src/detection/fp_utils.cc index 90989638a..e0ec958ef 100644 --- a/src/detection/fp_utils.cc +++ b/src/detection/fp_utils.cc @@ -107,6 +107,9 @@ PmType get_pm_type(CursorActionType cat) case CAT_SET_KEY: return PM_TYPE_KEY; + case CAT_SET_VBA: + return PM_TYPE_VBA; + default: break; } @@ -152,6 +155,9 @@ static const char* get_service(const char* opt) if ( !strncmp(opt, "sip_", 4) ) return "sip"; + if ( !strncmp(opt, "vba_data", 8) ) + return "file"; + return nullptr; } diff --git a/src/framework/inspector.h b/src/framework/inspector.h index 86091ab46..8e4171b0d 100644 --- a/src/framework/inspector.h +++ b/src/framework/inspector.h @@ -47,7 +47,7 @@ struct InspectionBuffer // FIXIT-L file data is tbd IBT_KEY, IBT_HEADER, IBT_BODY, IBT_FILE, IBT_ALT, IBT_RAW_KEY, IBT_RAW_HEADER, IBT_METHOD, IBT_STAT_CODE, - IBT_STAT_MSG, IBT_COOKIE, IBT_MAX + IBT_STAT_MSG, IBT_COOKIE, IBT_VBA, IBT_MAX }; const uint8_t* data; unsigned len; diff --git a/src/framework/ips_option.h b/src/framework/ips_option.h index 9ffb003cc..978f442e6 100644 --- a/src/framework/ips_option.h +++ b/src/framework/ips_option.h @@ -63,6 +63,7 @@ enum CursorActionType CAT_SET_BODY, CAT_SET_HEADER, CAT_SET_KEY, + CAT_SET_VBA, }; enum RuleDirection diff --git a/src/ports/port_group.h b/src/ports/port_group.h index 7fd6c5d8e..fef2c6b9b 100644 --- a/src/ports/port_group.h +++ b/src/ports/port_group.h @@ -51,13 +51,14 @@ enum PmType PM_TYPE_STAT_CODE, PM_TYPE_STAT_MSG, PM_TYPE_COOKIE, + PM_TYPE_VBA, PM_TYPE_MAX }; const char* const pm_type_strings[PM_TYPE_MAX] = { "packet", "alt", "key", "header", "body", "file", "raw_key", "raw_header", - "method", "script", "stat_code", "stat_msg", "cookie" + "method", "script", "stat_code", "stat_msg", "cookie" , "vba" }; struct RULE_NODE diff --git a/src/service_inspectors/http_inspect/http_api.cc b/src/service_inspectors/http_inspect/http_api.cc index 9cae42e56..8e6f5f03e 100644 --- a/src/service_inspectors/http_inspect/http_api.cc +++ b/src/service_inspectors/http_inspect/http_api.cc @@ -67,6 +67,7 @@ const char* HttpApi::classic_buffer_names[] = "http_true_ip", "http_uri", "http_version", + "vba_data", nullptr }; @@ -116,6 +117,7 @@ extern const BaseApi* ips_http_trailer; extern const BaseApi* ips_http_true_ip; extern const BaseApi* ips_http_uri; extern const BaseApi* ips_http_version; +extern const BaseApi* ips_vba_data; #ifdef BUILDING_SO SO_PUBLIC const BaseApi* snort_plugins[] = @@ -142,6 +144,7 @@ const BaseApi* sin_http[] = ips_http_true_ip, ips_http_uri, ips_http_version, + ips_vba_data, nullptr }; diff --git a/src/service_inspectors/http_inspect/http_enum.h b/src/service_inspectors/http_inspect/http_enum.h index 043cc23e2..811c91403 100755 --- a/src/service_inspectors/http_inspect/http_enum.h +++ b/src/service_inspectors/http_inspect/http_enum.h @@ -60,7 +60,7 @@ enum HTTP_BUFFER { HTTP_BUFFER_CLIENT_BODY = 1, HTTP_BUFFER_COOKIE, HTTP_BUFFER_ HTTP_BUFFER_RAW_HEADER, HTTP_BUFFER_RAW_REQUEST, HTTP_BUFFER_RAW_STATUS, HTTP_BUFFER_RAW_TRAILER, HTTP_BUFFER_RAW_URI, HTTP_BUFFER_STAT_CODE, HTTP_BUFFER_STAT_MSG, HTTP_BUFFER_TRAILER, HTTP_BUFFER_TRUE_IP, HTTP_BUFFER_URI, HTTP_BUFFER_VERSION, - HTTP_BUFFER_MAX }; + BUFFER_VBA_DATA, HTTP_BUFFER_MAX }; // Peg counts // This enum must remain synchronized with HttpModule::peg_names[] in http_tables.cc diff --git a/src/service_inspectors/http_inspect/http_inspect.cc b/src/service_inspectors/http_inspect/http_inspect.cc index 3060bab20..ddb9098fc 100755 --- a/src/service_inspectors/http_inspect/http_inspect.cc +++ b/src/service_inspectors/http_inspect/http_inspect.cc @@ -154,6 +154,7 @@ void HttpInspect::show(const SnortConfig*) const ConfigLogger::log_flag("decompress_pdf", params->decompress_pdf); ConfigLogger::log_flag("decompress_swf", params->decompress_swf); ConfigLogger::log_flag("decompress_zip", params->decompress_zip); + ConfigLogger::log_flag("decompress_vba", params->decompress_vba); ConfigLogger::log_flag("script_detection", params->script_detection); ConfigLogger::log_flag("normalize_javascript", params->js_norm_param.normalize_javascript); ConfigLogger::log_value("max_javascript_whitespaces", @@ -242,6 +243,9 @@ bool HttpInspect::get_buf(InspectionBuffer::Type ibt, Packet* p, InspectionBuffe case InspectionBuffer::IBT_COOKIE: return get_buf(HTTP_BUFFER_COOKIE, p , b); + case InspectionBuffer::IBT_VBA: + return get_buf(BUFFER_VBA_DATA, p, b); + default: return false; } @@ -296,6 +300,7 @@ bool HttpInspect::get_fp_buf(InspectionBuffer::Type ibt, Packet* p, InspectionBu return false; break; case InspectionBuffer::IBT_BODY: + case InspectionBuffer::IBT_VBA: if ((get_latest_is(p) != IS_FIRST_BODY) && (get_latest_is(p) != IS_BODY)) return false; break; diff --git a/src/service_inspectors/http_inspect/http_module.cc b/src/service_inspectors/http_inspect/http_module.cc index e92461950..af0729769 100755 --- a/src/service_inspectors/http_inspect/http_module.cc +++ b/src/service_inspectors/http_inspect/http_module.cc @@ -74,6 +74,9 @@ const Parameter HttpModule::http_params[] = { "decompress_zip", Parameter::PT_BOOL, nullptr, "false", "decompress zip files in response bodies" }, + { "decompress_vba", Parameter::PT_BOOL, nullptr, "false", + "decompress MS Office Visual Basic for Applications macro files in response bodies" }, + { "script_detection", Parameter::PT_BOOL, nullptr, "false", "inspect JavaScript immediately upon script end" }, @@ -238,6 +241,10 @@ bool HttpModule::set(const char*, Value& val, SnortConfig*) { params->decompress_zip = val.get_bool(); } + else if (val.is("decompress_vba")) + { + params->decompress_vba = val.get_bool(); + } else if (val.is("script_detection")) { params->script_detection = val.get_bool(); diff --git a/src/service_inspectors/http_inspect/http_module.h b/src/service_inspectors/http_inspect/http_module.h index ac27843dc..01e10c1f2 100755 --- a/src/service_inspectors/http_inspect/http_module.h +++ b/src/service_inspectors/http_inspect/http_module.h @@ -55,6 +55,7 @@ public: bool decompress_pdf = false; bool decompress_swf = false; bool decompress_zip = false; + bool decompress_vba = false; bool script_detection = false; snort::LiteralSearch::Handle* script_detection_handle = nullptr; bool publish_request_body = true; diff --git a/src/service_inspectors/http_inspect/http_msg_body.cc b/src/service_inspectors/http_inspect/http_msg_body.cc index fc16227c3..2a364ff60 100644 --- a/src/service_inspectors/http_inspect/http_msg_body.cc +++ b/src/service_inspectors/http_inspect/http_msg_body.cc @@ -23,6 +23,7 @@ #include "http_msg_body.h" +#include "decompress/file_olefile.h" #include "file_api/file_flows.h" #include "file_api/file_service.h" #include "pub_sub/http_request_body_event.h" @@ -514,6 +515,30 @@ const Field& HttpMsgBody::get_classic_client_body() return classic_normalize(detect_data, classic_client_body, false, params->uri_param); } +const Field& HttpMsgBody::get_decomp_vba_data() +{ + if (decompressed_vba_data.length() != STAT_NOT_COMPUTE) + return decompressed_vba_data; + + if (!session_data->fd_state->ole_data_ptr || !session_data->fd_state->ole_data_len) + return Field::FIELD_NULL; + + uint8_t* buf = nullptr; + uint32_t buf_len = 0; + + oleprocess(session_data->fd_state->ole_data_ptr, session_data->fd_state->ole_data_len, buf, + buf_len); + if (buf && buf_len) + decompressed_vba_data.set(buf_len, buf, true); + else + decompressed_vba_data.set(STAT_NOT_PRESENT); + + session_data->fd_state->ole_data_ptr = nullptr; + session_data->fd_state->ole_data_len = 0; + + return decompressed_vba_data; +} + int32_t HttpMsgBody::get_publish_length() const { return publish_length; diff --git a/src/service_inspectors/http_inspect/http_msg_body.h b/src/service_inspectors/http_inspect/http_msg_body.h index 460b44a7d..ee1327181 100644 --- a/src/service_inspectors/http_inspect/http_msg_body.h +++ b/src/service_inspectors/http_inspect/http_msg_body.h @@ -38,6 +38,7 @@ public: bool detection_required() const override { return (detect_data.length() > 0); } HttpMsgBody* get_body() override { return this; } const Field& get_classic_client_body(); + const Field& get_decomp_vba_data(); const Field& get_detect_data() { return detect_data; } const Field& get_msg_text_new() const { return msg_text_new; } static void fd_event_callback(void* context, int event); @@ -80,6 +81,7 @@ private: Field detect_data; Field enhanced_js_norm_body; Field classic_client_body; // URI normalization applied + Field decompressed_vba_data; int32_t publish_length = HttpCommon::STAT_NOT_PRESENT; }; diff --git a/src/service_inspectors/http_inspect/http_msg_header.cc b/src/service_inspectors/http_inspect/http_msg_header.cc index 724b9546a..e4ea11165 100755 --- a/src/service_inspectors/http_inspect/http_msg_header.cc +++ b/src/service_inspectors/http_inspect/http_msg_header.cc @@ -665,7 +665,8 @@ void HttpMsgHeader::setup_file_decompression() session_data->fd_state->Modes = (params->decompress_pdf ? FILE_PDF_DEFL_BIT : 0) | (params->decompress_swf ? (FILE_SWF_ZLIB_BIT | FILE_SWF_LZMA_BIT) : 0) | - (params->decompress_zip ? FILE_ZIP_DEFL_BIT : 0); + (params->decompress_zip ? FILE_ZIP_DEFL_BIT : 0) | + (params->decompress_vba ? FILE_VBA_EXTR_BIT : 0); session_data->fd_state->Alert_Callback = HttpMsgBody::fd_event_callback; session_data->fd_state->Alert_Context = &session_data->fd_alert_context; session_data->fd_state->Compr_Depth = 0; diff --git a/src/service_inspectors/http_inspect/http_msg_section.cc b/src/service_inspectors/http_inspect/http_msg_section.cc index 0f6922f80..fa0bc52da 100644 --- a/src/service_inspectors/http_inspect/http_msg_section.cc +++ b/src/service_inspectors/http_inspect/http_msg_section.cc @@ -382,6 +382,14 @@ const Field& HttpMsgSection::get_classic_buffer(Cursor& c, const HttpBufferInfo& (HttpMsgStart*)request : (HttpMsgStart*)status; return (start != nullptr) ? start->get_version() : Field::FIELD_NULL; } + case BUFFER_VBA_DATA: + { + HttpMsgBody* msg_body = get_body(); + if (session_data->fd_state and msg_body) + return msg_body->get_decomp_vba_data(); + else + return Field::FIELD_NULL; + } default: assert(false); return Field::FIELD_NULL; diff --git a/src/service_inspectors/http_inspect/ips_http.cc b/src/service_inspectors/http_inspect/ips_http.cc index 5940ac00c..3a4a9af8d 100644 --- a/src/service_inspectors/http_inspect/ips_http.cc +++ b/src/service_inspectors/http_inspect/ips_http.cc @@ -70,6 +70,7 @@ bool HttpCursorModule::begin(const char*, int, SnortConfig*) break; case HTTP_BUFFER_CLIENT_BODY: case HTTP_BUFFER_RAW_BODY: + case BUFFER_VBA_DATA: inspect_section = IS_BODY; break; case HTTP_BUFFER_RAW_TRAILER: @@ -1201,6 +1202,46 @@ static const IpsApi version_api = nullptr }; +//------------------------------------------------------------------------- +// vba_data +//------------------------------------------------------------------------- +// + +#undef IPS_OPT +#define IPS_OPT "vba_data" +#undef IPS_HELP +#define IPS_HELP "rule option to set the detection cursor to the MS Office Visual Basic for Applications macros buffer" +static Module* vba_data_mod_ctor() +{ + return new HttpCursorModule(IPS_OPT, IPS_HELP, BUFFER_VBA_DATA, CAT_SET_VBA, + PSI_VBA_DATA); +} + +static const IpsApi vba_data_api = +{ + { + PT_IPS_OPTION, + sizeof(IpsApi), + IPSAPI_VERSION, + 1, + API_RESERVED, + API_OPTIONS, + IPS_OPT, + IPS_HELP, + vba_data_mod_ctor, + HttpCursorModule::mod_dtor + }, + OPT_TYPE_DETECTION, + 0, PROTO_BIT__TCP, + nullptr, + nullptr, + nullptr, + nullptr, + HttpIpsOption::opt_ctor, + HttpIpsOption::opt_dtor, + nullptr +}; + //------------------------------------------------------------------------- // plugins //------------------------------------------------------------------------- @@ -1223,4 +1264,5 @@ const BaseApi* ips_http_trailer = &trailer_api.base; const BaseApi* ips_http_true_ip = &true_ip_api.base; const BaseApi* ips_http_uri = &uri_api.base; const BaseApi* ips_http_version = &version_api.base; +const BaseApi* ips_vba_data = &vba_data_api.base; diff --git a/src/service_inspectors/http_inspect/ips_http.h b/src/service_inspectors/http_inspect/ips_http.h index 25d9d60c8..81174d236 100644 --- a/src/service_inspectors/http_inspect/ips_http.h +++ b/src/service_inspectors/http_inspect/ips_http.h @@ -32,7 +32,7 @@ enum PsIdx { PSI_CLIENT_BODY, PSI_COOKIE, PSI_HEADER, PSI_METHOD, PSI_PARAM, PSI_RAW_BODY, PSI_RAW_COOKIE, PSI_RAW_HEADER, PSI_RAW_REQUEST, PSI_RAW_STATUS, PSI_RAW_TRAILER, PSI_RAW_URI, PSI_STAT_CODE, PSI_STAT_MSG, PSI_TRAILER, - PSI_TRUE_IP, PSI_URI, PSI_VERSION, PSI_MAX }; + PSI_TRUE_IP, PSI_URI, PSI_VERSION, PSI_VBA_DATA, PSI_MAX }; class HttpCursorModule : public snort::Module { diff --git a/src/utils/endian.h b/src/utils/endian.h index 0610654f5..39ea8aca7 100644 --- a/src/utils/endian.h +++ b/src/utils/endian.h @@ -60,4 +60,21 @@ (uint32_t)(*((const uint8_t*)(p) + 1) << 8) | \ (uint32_t)(*((const uint8_t*)(p)))) +#define LETOHLL_UNALIGNED(p) \ + (((uint64_t)(LETOHL_UNALIGNED(p + 4)) << 32) | ((uint64_t)(LETOHL_UNALIGNED(p)))) + +#define BETOHS_UNALIGNED(p) \ + ((uint16_t)(*((const uint8_t*)(p)) << 8) | \ + (uint16_t)(*((const uint8_t*)(p) + 1))) + +#define BETOHL_UNALIGNED(p) \ + ((uint32_t)(*((const uint8_t*)(p)) << 24) | \ + (uint32_t)(*((const uint8_t*)(p) + 1) << 16) | \ + (uint32_t)(*((const uint8_t*)(p) + 2) << 8) | \ + (uint32_t)(*((const uint8_t*)(p) + 3))) + +#define BETOHLL_UNALIGNED(p) \ + (((uint64_t)(BETOHL_UNALIGNED(p)) << 32) | ((uint64_t)(BETOHL_UNALIGNED(p + 4)))) + #endif + diff --git a/src/utils/stats.cc b/src/utils/stats.cc index 344fd9135..470f44948 100644 --- a/src/utils/stats.cc +++ b/src/utils/stats.cc @@ -202,6 +202,7 @@ const PegInfo pc_names[] = { CountType::SUM, "stat_code_searches", "fast pattern searches in status code buffer" }, { CountType::SUM, "stat_msg_searches", "fast pattern searches in status message buffer" }, { CountType::SUM, "cookie_searches", "fast pattern searches in cookie buffer" }, + { CountType::SUM, "vba_searches", "fast pattern searches in MS Office Visual Basic for Applications buffer" }, { CountType::SUM, "offloads", "fast pattern searches that were offloaded" }, { CountType::SUM, "alerts", "alerts not including IP reputation" }, { CountType::SUM, "total_alerts", "alerts including IP reputation" }, diff --git a/src/utils/stats.h b/src/utils/stats.h index 50c825ecb..947ad2b39 100644 --- a/src/utils/stats.h +++ b/src/utils/stats.h @@ -53,6 +53,7 @@ struct PacketCount PegCount stat_code_searches; PegCount stat_msg_searches; PegCount cookie_searches; + PegCount vba_searches; PegCount offloads; PegCount alert_pkts; PegCount total_alert_pkts;