/src/suricata7/src/detect-cipservice.c
Line | Count | Source |
1 | | /* Copyright (C) 2015 Open Information Security Foundation |
2 | | * |
3 | | * You can copy, redistribute or modify this Program under the terms of |
4 | | * the GNU General Public License version 2 as published by the Free |
5 | | * Software Foundation. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * version 2 along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 | | * 02110-1301, USA. |
16 | | */ |
17 | | |
18 | | /** |
19 | | * \file |
20 | | * |
21 | | * \author Kevin Wong <kwong@solananetworks.com> |
22 | | * |
23 | | * Set up ENIP Command and CIP Service rule parsing and entry point for matching |
24 | | */ |
25 | | |
26 | | #include "suricata-common.h" |
27 | | #include "util-unittest.h" |
28 | | #include "detect-parse.h" |
29 | | #include "detect-engine.h" |
30 | | #include "util-byte.h" |
31 | | |
32 | | #include "app-layer-enip-common.h" |
33 | | #include "detect-cipservice.h" |
34 | | #include "detect-engine-enip.h" |
35 | | |
36 | | /* |
37 | | * CIP SERVICE CODE |
38 | | */ |
39 | | |
40 | | /** |
41 | | * \brief CIP Service Detect Prototypes |
42 | | */ |
43 | | static int DetectCipServiceSetup(DetectEngineCtx *, Signature *, const char *); |
44 | | static void DetectCipServiceFree(DetectEngineCtx *, void *); |
45 | | #ifdef UNITTESTS |
46 | | static void DetectCipServiceRegisterTests(void); |
47 | | #endif |
48 | | static int g_cip_buffer_id = 0; |
49 | | |
50 | | /** |
51 | | * \brief Registration function for cip_service: keyword |
52 | | */ |
53 | | void DetectCipServiceRegister(void) |
54 | 34 | { |
55 | 34 | SCEnter(); |
56 | 34 | sigmatch_table[DETECT_CIPSERVICE].name = "cip_service"; //rule keyword |
57 | 34 | sigmatch_table[DETECT_CIPSERVICE].desc = "match on CIP Service"; |
58 | 34 | sigmatch_table[DETECT_CIPSERVICE].url = "/rules/enip-keyword.html#enip-cip-keywords"; |
59 | 34 | sigmatch_table[DETECT_CIPSERVICE].Match = NULL; |
60 | 34 | sigmatch_table[DETECT_CIPSERVICE].Setup = DetectCipServiceSetup; |
61 | 34 | sigmatch_table[DETECT_CIPSERVICE].Free = DetectCipServiceFree; |
62 | | #ifdef UNITTESTS |
63 | | sigmatch_table[DETECT_CIPSERVICE].RegisterTests |
64 | | = DetectCipServiceRegisterTests; |
65 | | #endif |
66 | 34 | DetectAppLayerInspectEngineRegister2( |
67 | 34 | "cip", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, DetectEngineInspectCIP, NULL); |
68 | 34 | DetectAppLayerInspectEngineRegister2( |
69 | 34 | "cip", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, DetectEngineInspectCIP, NULL); |
70 | | |
71 | 34 | g_cip_buffer_id = DetectBufferTypeGetByName("cip"); |
72 | | |
73 | 34 | SCReturn; |
74 | 34 | } |
75 | | |
76 | | /** |
77 | | * \brief This function is used to parse cip_service options passed via cip_service: keyword |
78 | | * |
79 | | * \param rulestr Pointer to the user provided rulestr options |
80 | | * Takes comma seperated string with numeric tokens. Only first 3 are used |
81 | | * |
82 | | * \retval cipserviced pointer to DetectCipServiceData on success |
83 | | * \retval NULL on failure |
84 | | */ |
85 | | static DetectCipServiceData *DetectCipServiceParse(const char *rulestrc) |
86 | 7.08k | { |
87 | 7.08k | const char delims[] = ","; |
88 | 7.08k | DetectCipServiceData *cipserviced = NULL; |
89 | | |
90 | | //SCLogDebug("DetectCipServiceParse - rule string %s", rulestr); |
91 | | |
92 | | /* strtok_r modifies the string so work with a copy */ |
93 | 7.08k | char *rulestr = SCStrdup(rulestrc); |
94 | 7.08k | if (unlikely(rulestr == NULL)) |
95 | 0 | goto error; |
96 | | |
97 | 7.08k | cipserviced = SCMalloc(sizeof(DetectCipServiceData)); |
98 | 7.08k | if (unlikely(cipserviced == NULL)) |
99 | 0 | goto error; |
100 | | |
101 | 7.08k | cipserviced->cipservice = 0; |
102 | 7.08k | cipserviced->cipclass = 0; |
103 | 7.08k | cipserviced->matchattribute = 1; |
104 | 7.08k | cipserviced->cipattribute = 0; |
105 | | |
106 | 7.08k | char* token; |
107 | 7.08k | char *save; |
108 | 7.08k | uint8_t var; |
109 | 7.08k | uint8_t input[3] = { 0, 0, 0 }; |
110 | 7.08k | uint8_t i = 0; |
111 | | |
112 | 7.08k | token = strtok_r(rulestr, delims, &save); |
113 | 13.5k | while (token != NULL) |
114 | 7.22k | { |
115 | 7.22k | if (i > 2) //for now only need 3 parameters |
116 | 0 | { |
117 | 0 | SCLogError("too many parameters"); |
118 | 0 | goto error; |
119 | 0 | } |
120 | | |
121 | 7.22k | if (i < 2) //if on service or class |
122 | 7.15k | { |
123 | 7.15k | if (!isdigit((int) *token)) |
124 | 597 | { |
125 | 597 | SCLogError("parameter error %s", token); |
126 | 597 | goto error; |
127 | 597 | } |
128 | 7.15k | } else //if on attribute |
129 | 67 | { |
130 | | |
131 | 67 | if (token[0] == '!') |
132 | 62 | { |
133 | 62 | cipserviced->matchattribute = 0; |
134 | 62 | token++; |
135 | 62 | } |
136 | | |
137 | 67 | if (!isdigit((int) *token)) |
138 | 62 | { |
139 | 62 | SCLogError("attribute error %s", token); |
140 | 62 | goto error; |
141 | 62 | } |
142 | | |
143 | 67 | } |
144 | | |
145 | 6.56k | unsigned long num = atol(token); |
146 | 6.56k | if ((num > MAX_CIP_SERVICE) && (i == 0))//if service greater than 7 bit |
147 | 128 | { |
148 | 128 | SCLogError("invalid CIP service %lu", num); |
149 | 128 | goto error; |
150 | 6.43k | } else if ((num > MAX_CIP_CLASS) && (i == 1))//if service greater than 16 bit |
151 | 0 | { |
152 | 0 | SCLogError("invalid CIP class %lu", num); |
153 | 0 | goto error; |
154 | 6.43k | } else if ((num > MAX_CIP_ATTRIBUTE) && (i == 2))//if service greater than 16 bit |
155 | 0 | { |
156 | 0 | SCLogError("invalid CIP attribute %lu", num); |
157 | 0 | goto error; |
158 | 0 | } |
159 | | |
160 | 6.43k | sscanf(token, "%2" SCNu8, &var); |
161 | 6.43k | input[i++] = var; |
162 | | |
163 | 6.43k | token = strtok_r(NULL, delims, &save); |
164 | 6.43k | } |
165 | | |
166 | 6.29k | if (i == 0) { |
167 | 0 | SCLogError("no tokens found"); |
168 | 0 | goto error; |
169 | 0 | } |
170 | | |
171 | 6.29k | cipserviced->cipservice = input[0]; |
172 | 6.29k | cipserviced->cipclass = input[1]; |
173 | 6.29k | cipserviced->cipattribute = input[2]; |
174 | 6.29k | cipserviced->tokens = i; |
175 | | |
176 | 6.29k | SCLogDebug("DetectCipServiceParse - tokens %d", cipserviced->tokens); |
177 | 6.29k | SCLogDebug("DetectCipServiceParse - service %d", cipserviced->cipservice); |
178 | 6.29k | SCLogDebug("DetectCipServiceParse - class %d", cipserviced->cipclass); |
179 | 6.29k | SCLogDebug("DetectCipServiceParse - match attribute %d", |
180 | 6.29k | cipserviced->matchattribute); |
181 | 6.29k | SCLogDebug("DetectCipServiceParse - attribute %d", |
182 | 6.29k | cipserviced->cipattribute); |
183 | | |
184 | 6.29k | SCFree(rulestr); |
185 | 6.29k | SCReturnPtr(cipserviced, "DetectENIPFunction"); |
186 | | |
187 | 787 | error: |
188 | 787 | if (cipserviced) |
189 | 787 | SCFree(cipserviced); |
190 | 787 | if (rulestr) |
191 | 787 | SCFree(rulestr); |
192 | 787 | SCReturnPtr(NULL, "DetectENIP"); |
193 | 6.29k | } |
194 | | |
195 | | /** |
196 | | * \brief this function is used to a cipserviced the parsed cip_service data into the current signature |
197 | | * |
198 | | * \param de_ctx pointer to the Detection Engine Context |
199 | | * \param s pointer to the Current Signature |
200 | | * \param rulestr pointer to the user provided cip_service options |
201 | | * |
202 | | * \retval 0 on Success |
203 | | * \retval -1 on Failure |
204 | | */ |
205 | | static int DetectCipServiceSetup(DetectEngineCtx *de_ctx, Signature *s, |
206 | | const char *rulestr) |
207 | 7.20k | { |
208 | 7.20k | SCEnter(); |
209 | | |
210 | 7.20k | DetectCipServiceData *cipserviced = NULL; |
211 | 7.20k | SigMatch *sm = NULL; |
212 | | |
213 | 7.20k | if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) |
214 | 120 | return -1; |
215 | | |
216 | 7.08k | cipserviced = DetectCipServiceParse(rulestr); |
217 | 7.08k | if (cipserviced == NULL) |
218 | 787 | goto error; |
219 | | |
220 | 6.29k | sm = SigMatchAlloc(); |
221 | 6.29k | if (sm == NULL) |
222 | 0 | goto error; |
223 | | |
224 | 6.29k | sm->type = DETECT_CIPSERVICE; |
225 | 6.29k | sm->ctx = (void *) cipserviced; |
226 | | |
227 | 6.29k | SigMatchAppendSMToList(s, sm, g_cip_buffer_id); |
228 | 6.29k | SCReturnInt(0); |
229 | | |
230 | 787 | error: |
231 | 787 | if (cipserviced != NULL) |
232 | 0 | DetectCipServiceFree(de_ctx, cipserviced); |
233 | 787 | if (sm != NULL) |
234 | 0 | SCFree(sm); |
235 | 787 | SCReturnInt(-1); |
236 | 6.29k | } |
237 | | |
238 | | /** |
239 | | * \brief this function will free memory associated with DetectCipServiceData |
240 | | * |
241 | | * \param ptr pointer to DetectCipServiceData |
242 | | */ |
243 | | static void DetectCipServiceFree(DetectEngineCtx *de_ctx, void *ptr) |
244 | 6.29k | { |
245 | 6.29k | DetectCipServiceData *cipserviced = (DetectCipServiceData *) ptr; |
246 | 6.29k | SCFree(cipserviced); |
247 | 6.29k | } |
248 | | |
249 | | #ifdef UNITTESTS |
250 | | |
251 | | /** |
252 | | * \test Test CIP Command parameter parsing |
253 | | */ |
254 | | static int DetectCipServiceParseTest01 (void) |
255 | | { |
256 | | DetectCipServiceData *cipserviced = NULL; |
257 | | cipserviced = DetectCipServiceParse("7"); |
258 | | FAIL_IF_NULL(cipserviced); |
259 | | FAIL_IF(cipserviced->cipservice != 7); |
260 | | DetectCipServiceFree(NULL, cipserviced); |
261 | | PASS; |
262 | | } |
263 | | |
264 | | /** |
265 | | * \test Test CIP Service signature |
266 | | */ |
267 | | static int DetectCipServiceSignatureTest01 (void) |
268 | | { |
269 | | DetectEngineCtx *de_ctx = DetectEngineCtxInit(); |
270 | | FAIL_IF_NULL(de_ctx); |
271 | | Signature *sig = DetectEngineAppendSig(de_ctx, "alert tcp any any -> any any (cip_service:1; sid:1; rev:1;)"); |
272 | | FAIL_IF_NULL(sig); |
273 | | DetectEngineCtxFree(de_ctx); |
274 | | PASS; |
275 | | } |
276 | | |
277 | | /** |
278 | | * \brief this function registers unit tests for DetectCipService |
279 | | */ |
280 | | static void DetectCipServiceRegisterTests(void) |
281 | | { |
282 | | UtRegisterTest("DetectCipServiceParseTest01", |
283 | | DetectCipServiceParseTest01); |
284 | | UtRegisterTest("DetectCipServiceSignatureTest01", |
285 | | DetectCipServiceSignatureTest01); |
286 | | } |
287 | | #endif /* UNITTESTS */ |
288 | | |
289 | | /* |
290 | | * ENIP COMMAND CODE |
291 | | */ |
292 | | |
293 | | /** |
294 | | * \brief ENIP Command Detect Prototypes |
295 | | */ |
296 | | static int DetectEnipCommandSetup(DetectEngineCtx *, Signature *, const char *); |
297 | | static void DetectEnipCommandFree(DetectEngineCtx *, void *); |
298 | | #ifdef UNITTESTS |
299 | | static void DetectEnipCommandRegisterTests(void); |
300 | | #endif |
301 | | static int g_enip_buffer_id = 0; |
302 | | |
303 | | /** |
304 | | * \brief Registration function for enip_command: keyword |
305 | | */ |
306 | | void DetectEnipCommandRegister(void) |
307 | 34 | { |
308 | 34 | sigmatch_table[DETECT_ENIPCOMMAND].name = "enip_command"; //rule keyword |
309 | 34 | sigmatch_table[DETECT_ENIPCOMMAND].desc |
310 | 34 | = "rules for detecting EtherNet/IP command"; |
311 | 34 | sigmatch_table[DETECT_ENIPCOMMAND].url = "/rules/enip-keyword.html#enip-cip-keywords"; |
312 | 34 | sigmatch_table[DETECT_ENIPCOMMAND].Match = NULL; |
313 | 34 | sigmatch_table[DETECT_ENIPCOMMAND].Setup = DetectEnipCommandSetup; |
314 | 34 | sigmatch_table[DETECT_ENIPCOMMAND].Free = DetectEnipCommandFree; |
315 | | #ifdef UNITTESTS |
316 | | sigmatch_table[DETECT_ENIPCOMMAND].RegisterTests |
317 | | = DetectEnipCommandRegisterTests; |
318 | | #endif |
319 | 34 | DetectAppLayerInspectEngineRegister2( |
320 | 34 | "enip", ALPROTO_ENIP, SIG_FLAG_TOSERVER, 0, DetectEngineInspectENIP, NULL); |
321 | 34 | DetectAppLayerInspectEngineRegister2( |
322 | 34 | "enip", ALPROTO_ENIP, SIG_FLAG_TOCLIENT, 0, DetectEngineInspectENIP, NULL); |
323 | | |
324 | 34 | g_enip_buffer_id = DetectBufferTypeGetByName("enip"); |
325 | 34 | } |
326 | | |
327 | | /** |
328 | | * \brief This function is used to parse cip_service options passed via enip_command: keyword |
329 | | * |
330 | | * \param rulestr Pointer to the user provided rulestr options |
331 | | * Takes single numeric value |
332 | | * |
333 | | * \retval enipcmdd pointer to DetectCipServiceData on success |
334 | | * \retval NULL on failure |
335 | | */ |
336 | | static DetectEnipCommandData *DetectEnipCommandParse(const char *rulestr) |
337 | 1.27k | { |
338 | 1.27k | DetectEnipCommandData *enipcmdd = NULL; |
339 | | |
340 | 1.27k | enipcmdd = SCMalloc(sizeof(DetectEnipCommandData)); |
341 | 1.27k | if (unlikely(enipcmdd == NULL)) |
342 | 0 | goto error; |
343 | | |
344 | 1.27k | if (!(isdigit((int) *rulestr))) { |
345 | 228 | SCLogError("invalid ENIP command %s", rulestr); |
346 | 228 | goto error; |
347 | 228 | } |
348 | | |
349 | 1.04k | uint16_t cmd; |
350 | 1.04k | if (StringParseUint16(&cmd, 10, 0, rulestr) <= 0) { |
351 | 394 | SCLogError("invalid ENIP command" |
352 | 394 | ": \"%s\"", |
353 | 394 | rulestr); |
354 | 394 | goto error; |
355 | 394 | } |
356 | | |
357 | 653 | enipcmdd->enipcommand = cmd; |
358 | | |
359 | 653 | return enipcmdd; |
360 | | |
361 | 622 | error: |
362 | 622 | if (enipcmdd) |
363 | 622 | SCFree(enipcmdd); |
364 | 622 | return NULL; |
365 | 1.04k | } |
366 | | |
367 | | /** |
368 | | * \brief this function is used by enipcmdd to parse enip_command data into the current signature |
369 | | * |
370 | | * \param de_ctx pointer to the Detection Engine Context |
371 | | * \param s pointer to the Current Signature |
372 | | * \param rulestr pointer to the user provided enip command options |
373 | | * |
374 | | * \retval 0 on Success |
375 | | * \retval -1 on Failure |
376 | | */ |
377 | | static int DetectEnipCommandSetup(DetectEngineCtx *de_ctx, Signature *s, |
378 | | const char *rulestr) |
379 | 1.33k | { |
380 | 1.33k | DetectEnipCommandData *enipcmdd = NULL; |
381 | 1.33k | SigMatch *sm = NULL; |
382 | | |
383 | 1.33k | if (DetectSignatureSetAppProto(s, ALPROTO_ENIP) != 0) |
384 | 64 | return -1; |
385 | | |
386 | 1.27k | enipcmdd = DetectEnipCommandParse(rulestr); |
387 | 1.27k | if (enipcmdd == NULL) |
388 | 622 | goto error; |
389 | | |
390 | 653 | sm = SigMatchAlloc(); |
391 | 653 | if (sm == NULL) |
392 | 0 | goto error; |
393 | | |
394 | 653 | sm->type = DETECT_ENIPCOMMAND; |
395 | 653 | sm->ctx = (void *) enipcmdd; |
396 | | |
397 | 653 | SigMatchAppendSMToList(s, sm, g_enip_buffer_id); |
398 | 653 | SCReturnInt(0); |
399 | | |
400 | 622 | error: |
401 | 622 | if (enipcmdd != NULL) |
402 | 0 | DetectEnipCommandFree(de_ctx, enipcmdd); |
403 | 622 | if (sm != NULL) |
404 | 0 | SCFree(sm); |
405 | 622 | SCReturnInt(-1); |
406 | 653 | } |
407 | | |
408 | | /** |
409 | | * \brief this function will free memory associated with DetectEnipCommandData |
410 | | * |
411 | | * \param ptr pointer to DetectEnipCommandData |
412 | | */ |
413 | | static void DetectEnipCommandFree(DetectEngineCtx *de_ctx, void *ptr) |
414 | 653 | { |
415 | 653 | DetectEnipCommandData *enipcmdd = (DetectEnipCommandData *) ptr; |
416 | 653 | SCFree(enipcmdd); |
417 | 653 | } |
418 | | |
419 | | #ifdef UNITTESTS |
420 | | |
421 | | /** |
422 | | * \test ENIP parameter test |
423 | | */ |
424 | | |
425 | | static int DetectEnipCommandParseTest01 (void) |
426 | | { |
427 | | DetectEnipCommandData *enipcmdd = NULL; |
428 | | |
429 | | enipcmdd = DetectEnipCommandParse("1"); |
430 | | FAIL_IF_NULL(enipcmdd); |
431 | | FAIL_IF_NOT(enipcmdd->enipcommand == 1); |
432 | | |
433 | | DetectEnipCommandFree(NULL, enipcmdd); |
434 | | PASS; |
435 | | } |
436 | | |
437 | | /** |
438 | | * \test ENIP Command signature test |
439 | | */ |
440 | | static int DetectEnipCommandSignatureTest01 (void) |
441 | | { |
442 | | DetectEngineCtx *de_ctx = DetectEngineCtxInit(); |
443 | | FAIL_IF_NULL(de_ctx); |
444 | | |
445 | | Signature *sig = DetectEngineAppendSig(de_ctx, "alert tcp any any -> any any (enip_command:1; sid:1; rev:1;)"); |
446 | | FAIL_IF_NULL(sig); |
447 | | |
448 | | DetectEngineCtxFree(de_ctx); |
449 | | PASS; |
450 | | } |
451 | | |
452 | | /** |
453 | | * \brief this function registers unit tests for DetectEnipCommand |
454 | | */ |
455 | | static void DetectEnipCommandRegisterTests(void) |
456 | | { |
457 | | UtRegisterTest("DetectEnipCommandParseTest01", |
458 | | DetectEnipCommandParseTest01); |
459 | | UtRegisterTest("DetectEnipCommandSignatureTest01", |
460 | | DetectEnipCommandSignatureTest01); |
461 | | } |
462 | | #endif /* UNITTESTS */ |