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