1 /*
2  * This program is free software; you can redistribute it and/or modify
3  * it under the terms of the GNU General Public License Version 2 as
4  * published by the Free Software Foundation.  You may not use, modify or
5  * distribute this program under any other version of the GNU General
6  * Public License.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16  *
17  * Copyright (C) 2020-2020 Cisco and/or its affiliates. All rights reserved.
18  *
19  * Authors: Jeffrey Gu <jgu@cisco.com>, Pradeep Damodharan <prdamodh@cisco.com>
20  *
21  * Rule options for S7commplus preprocessor
22  *
23  */
24 
25 #include <string.h>
26 
27 #include "sf_types.h"
28 #include "sf_snort_plugin_api.h"
29 #include "sf_dynamic_preprocessor.h"
30 #include "spp_s7comm.h"
31 #include "s7comm_decode.h"
32 #include "s7comm_roptions.h"
33 #include "s7comm_paf.h"
34 
35 static s7commplus_opcode_map_t s7commplus_opcode_map[] =
36 {
37 	{"request",      0x31},
38 	{"response",     0x32},
39 	{"notification", 0x33},
40 	{"response2",    0x02}
41 };
42 
43 /* Mapping of name -> function code for 's7commplus_function' option. */
44 static s7commplus_func_map_t s7commplus_func_map[] =
45 {
46 	{"explore",          0x04BB},
47 	{"createobject",     0x04CA},
48 	{"deleteobject",     0x04D4},
49 	{"setvariable",      0x04F2},
50 	{"getlink",          0x0524},
51 	{"setmultivar",      0x0542},
52 	{"getmultivar",      0x054C},
53 	{"beginsequence",    0x0556},
54 	{"endsequence",      0x0560},
55 	{"invoke",           0x056B},
56 	{"getvarsubstr",     0x0586}
57 };
58 
S7commplusOpcodeInit(struct _SnortConfig * sc,char * name,char * params,void ** data)59 int S7commplusOpcodeInit(struct _SnortConfig *sc, char *name, char *params, void **data)
60 {
61 	char *endptr;
62 	s7commplus_option_data_t *s7commplus_data;
63 	unsigned int opcode = 0;
64 
65 	if (name == NULL || data == NULL)
66 		return 0;
67 
68 	if (strcmp(name, S7COMMPLUS_OPCODE_NAME) != 0)
69 		return 0;
70 
71 	if (params == NULL)
72 	{
73 		DynamicPreprocessorFatalMessage("%s(%d): No argument given for s7commplus_opcode. "
74 				"s7commplus_opcode requires a number between 0 and 0xFF.\n",
75 				*_dpd.config_file, *_dpd.config_line);
76 	}
77 
78 	s7commplus_data = (s7commplus_option_data_t *)calloc(1, sizeof(s7commplus_option_data_t));
79 	if (s7commplus_data == NULL)
80 	{
81 		DynamicPreprocessorFatalMessage("%s(%d) Failed to allocate memory for "
82 				"s7commplus_option_data_t data structure.\n", __FILE__, __LINE__);
83 	}
84 
85 	/* Parsing time */
86 	if (isdigit(params[0]))
87 	{
88 		/* argument given as integer (hexidecimal) */
89 		opcode = _dpd.SnortStrtoul(params, &endptr, 16);
90 		if ((opcode > 255) || (*endptr != '\0'))
91 		{
92 			DynamicPreprocessorFatalMessage("%s(%d): s7commplus_opcode requires a "
93 					"number between 0 and 0xFF.\n", *_dpd.config_file, *_dpd.config_line);
94 		}
95 	} else {
96 		/* Check the argument against the map */
97 		size_t i;
98 		int parse_success = 0;
99 		for (i = 0; i < (sizeof(s7commplus_opcode_map) / sizeof(s7commplus_opcode_map_t)); i++)
100 		{
101 			if (strcmp(params, s7commplus_opcode_map[i].name) == 0)
102 			{
103 				parse_success = 1;
104 				opcode = s7commplus_opcode_map[i].opcode;
105 				break;
106 			}
107 		}
108 
109 		if (!parse_success)
110 		{
111 			DynamicPreprocessorFatalMessage("%s(%d): s7commplus_opcode requires a "
112 					"number between 0 and 0xFF, or a valid opcode name.\n",
113 					*_dpd.config_file, *_dpd.config_line);
114 		}
115 	}
116 
117 	s7commplus_data->type = S7COMMPLUS_OPCODE;
118 	s7commplus_data->arg = (uint8_t) opcode;
119 
120 	*data = (void *)s7commplus_data;
121 
122 	return 1;
123 }
124 
S7commplusFuncInit(struct _SnortConfig * sc,char * name,char * params,void ** data)125 int S7commplusFuncInit(struct _SnortConfig *sc, char *name, char *params, void **data)
126 {
127 	char *endptr;
128 	s7commplus_option_data_t *s7commplus_data;
129 	unsigned int func = 0;
130 
131 	if (name == NULL || data == NULL)
132 		return 0;
133 
134 	if (strcmp(name, S7COMMPLUS_FUNC_NAME) != 0)
135 		return 0;
136 
137 	if (params == NULL)
138 	{
139 		DynamicPreprocessorFatalMessage("%s(%d): No argument given for s7commplus_function. "
140 				"s7commplus_function requires a number between 0 and 0xFFFF.\n",
141 				*_dpd.config_file, *_dpd.config_line);
142 	}
143 
144 	s7commplus_data = (s7commplus_option_data_t *)calloc(1, sizeof(s7commplus_option_data_t));
145 	if (s7commplus_data == NULL)
146 	{
147 		DynamicPreprocessorFatalMessage("%s(%d) Failed to allocate memory for "
148 				"s7commplus_option_data_t data structure.\n", __FILE__, __LINE__);
149 	}
150 
151 	/* Parsing time */
152 	if (isdigit(params[0]))
153 	{
154 		/* argument given as integer (hexidecimal) */
155 		func = _dpd.SnortStrtoul(params, &endptr, 16);
156 		if ((func > 0xFFFF) || (*endptr != '\0'))
157 		{
158 			DynamicPreprocessorFatalMessage("%s(%d): s7commplus_function requires a "
159 					"number between 0 and 0xFFFF.\n", *_dpd.config_file, *_dpd.config_line);
160 		}
161 	} else {
162 		/* Check the argument against the map */
163 		size_t i;
164 		int parse_success = 0;
165 		for (i = 0; i < (sizeof(s7commplus_func_map) / sizeof(s7commplus_func_map_t)); i++)
166 		{
167 			if (strcmp(params, s7commplus_func_map[i].name) == 0)
168 			{
169 				parse_success = 1;
170 				func = s7commplus_func_map[i].func;
171 				break;
172 			}
173 		}
174 
175 		if (!parse_success)
176 		{
177 			DynamicPreprocessorFatalMessage("%s(%d): s7commplus_function requires a "
178 					"number between 0 and 0xFFFF, or a valid function name.\n",
179 					*_dpd.config_file, *_dpd.config_line);
180 		}
181 	}
182 
183 	s7commplus_data->type = S7COMMPLUS_FUNC;
184 	s7commplus_data->arg = (uint16_t) func;
185 
186 	*data = (void *)s7commplus_data;
187 
188 	return 1;
189 }
190 
S7commplusContentInit(struct _SnortConfig * sc,char * name,char * params,void ** data)191 int S7commplusContentInit(struct _SnortConfig *sc, char *name, char *params, void **data)
192 {
193 	s7commplus_option_data_t *s7commplus_data;
194 
195 	if (strcmp(name, S7COMMPLUS_CONTENT_NAME) != 0)
196 		return 0;
197 
198 	/* Nothing to parse. */
199 	if (params)
200 	{
201 		DynamicPreprocessorFatalMessage("%s(%d): s7commplus_content does not take "
202 				"any arguments.\n", *_dpd.config_file, *_dpd.config_line);
203 	}
204 
205 	s7commplus_data = (s7commplus_option_data_t *)calloc(1, sizeof(s7commplus_option_data_t));
206 	if (s7commplus_data == NULL)
207 	{
208 		DynamicPreprocessorFatalMessage("%s(%d) Failed to allocate memory for "
209 				"s7commplus_option_data_t data structure.\n", __FILE__, __LINE__);
210 	}
211 
212 	s7commplus_data->type = S7COMMPLUS_CONTENT;
213 	s7commplus_data->arg = 0;
214 
215 	*data = (void *)s7commplus_data;
216 	return 1;
217 }
218 
219 /* S7commplus rule evaluation callback. */
S7commplusRuleEval(void * raw_packet,const uint8_t ** cursor,void * data)220 int S7commplusRuleEval(void *raw_packet, const uint8_t **cursor, void *data)
221 {
222 	SFSnortPacket *packet = (SFSnortPacket *)raw_packet;
223 	s7commplus_option_data_t *rule_data = (s7commplus_option_data_t *)data;
224 	s7commplus_session_data_t *session_data;
225 
226 	/*
227 	 * The preprocessor only evaluates PAF-flushed PDUs. If the rule options
228 	 * don't check for this, they'll fire on stale session data when the
229 	 * original packet goes through before flushing.
230 	 */
231 	if (!PacketHasFullPDU(packet) && S7commplusIsPafActive(packet))
232 		return RULE_NOMATCH;
233 
234 	session_data = (s7commplus_session_data_t *)
235 		_dpd.sessionAPI->get_application_data(packet->stream_session, PP_S7COMMPLUS);
236 
237 	if ((packet->payload_size == 0 ) || (session_data == NULL))
238 	{
239 		return RULE_NOMATCH;
240 	}
241 
242 	switch (rule_data->type)
243 	{
244 		case S7COMMPLUS_OPCODE:
245 			if (session_data->s7commplus_proto_id != S7COMMPLUS_PROTOCOL_ID)
246 				return RULE_NOMATCH;
247 			if (session_data->s7commplus_opcode == rule_data->arg)
248 				return RULE_MATCH;
249 			break;
250 
251 		case S7COMMPLUS_FUNC:
252 			if (session_data->s7commplus_proto_id != S7COMMPLUS_PROTOCOL_ID)
253 				return RULE_NOMATCH;
254 			if (session_data->s7commplus_function == rule_data->arg)
255 				return RULE_MATCH;
256 			break;
257 
258 		case S7COMMPLUS_CONTENT:
259 			if (session_data->s7commplus_proto_id != S7COMMPLUS_PROTOCOL_ID)
260 				return RULE_NOMATCH;
261 			if (packet->payload_size < TPKT_MIN_HDR_LEN + S7COMMPLUS_MIN_HDR_LEN)
262 				return RULE_NOMATCH;
263 
264 			/* S7commplus data */
265 			*cursor = (const uint8_t *) (packet->payload + TPKT_MIN_HDR_LEN + S7COMMPLUS_MIN_HDR_LEN);
266 			_dpd.SetAltDetect((uint8_t *)*cursor, (uint16_t)(packet->payload_size - TPKT_MIN_HDR_LEN - S7COMMPLUS_MIN_HDR_LEN));
267 
268 			return RULE_MATCH;
269 	}
270 
271 	return RULE_NOMATCH;
272 }
273