/* $Id$ */ /* ** Copyright (C) 2002-2006 Sourcefire, Inc. ** Author: Daniel Roelker ** ** 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /** ** @file sp_asn1_detect.c ** ** @author Daniel Roelker ** ** @brief Decode and detect ASN.1 types, lengths, and data. ** ** This detection plugin adds ASN.1 detection functions on a per rule ** basis. ASN.1 detection plugins can be added by editing this file and ** providing an interface in the configuration code. ** ** Detection Plugin Interface: ** ** asn1: [detection function],[arguments],[offset type],[size] ** ** Detection Functions: ** ** bitstring_overflow: no arguments ** double_overflow: no arguments ** oversize_length: max size (if no max size, then just return value) ** ** alert udp any any -> any 161 (msg:"foo"; \ ** asn1: oversize_length 10000, absolute_offset 0;) ** ** alert tcp any any -> any 162 (msg:"foo2"; \ ** asn1: bitstring_overflow, oversize_length 500, relative_offset 7;) ** ** ** Note that further general information about ASN.1 can be found in ** the file doc/README.asn1. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #ifndef SF_SNORT_ENGINE_DLL #include "debug.h" #else /* Ignore debug statements */ #include #define DEBUG_WRAP(x) #endif #include "../sfutil/asn1.h" #include "sp_asn1_detect.h" #define BITSTRING_OPT "bitstring_overflow" #define DOUBLE_OPT "double_overflow" #define LENGTH_OPT "oversize_length" #define DBL_FREE_OPT "double_free" #define ABS_OFFSET_OPT "absolute_offset" #define REL_OFFSET_OPT "relative_offset" #define PRINT_OPT "print" #define ABS_OFFSET 1 #define REL_OFFSET 2 #define DELIMITERS " ,\t\n" extern u_int8_t *doe_ptr; /* * Check to make sure that p is less than or equal to the ptr range * pointers * * 1 means it's in bounds, 0 means it's not */ static int inBounds(u_int8_t *start, u_int8_t *end, u_int8_t *p) { if(p >= start && p < end) { return 1; } return 0; } /* ** NAME ** BitStringOverflow:: */ /** ** The neccessary info to detect possible bitstring overflows. Thanks ** once again to microsoft for keeping us in business. ** ** @return integer ** ** @retval 0 failed ** @retval 1 detected */ static int BitStringOverflow(ASN1_TYPE *asn1, void * user) { if(!asn1) return 0; /* ** Here's what this means: ** ** If the ASN.1 type is a non-constructed bitstring (meaning that ** there is only one encoding, not multiple encodings). And ** the number of bits to ignore (this is taken from the first byte) ** is greater than the total number of bits, then we have an ** exploit attempt. */ if(asn1->ident.tag == SF_ASN1_TAG_BIT_STR && !asn1->ident.flag) { if(asn1->len.size && asn1->data && (((asn1->len.size - 1)<<3) < (unsigned int)asn1->data[0])) { return 1; } } return 0; } /* ** NAME ** DetectBitStringOverflow:: */ /** ** This is just a wrapper to the traverse function. It's important because ** this allows us to do more with individual nodes in the future. ** ** @return integer ** ** @retval 0 failed ** @rteval 1 detected */ static int DetectBitStringOverflow(ASN1_TYPE *asn1) { return asn1_traverse(asn1, NULL, BitStringOverflow); } /* ** NAME ** DoubleOverflow:: */ /** ** This is the info to detect double overflows. This may not be a ** remotely exploitable (remote services may not call the vulnerable ** microsoft function), but better safe than sorry. ** ** @return integer ** ** @retval 0 failed ** @retval 1 detected */ static int DoubleOverflow(ASN1_TYPE *asn1, void *user) { if(!asn1) return 0; /* ** Here's what this does. ** ** There is a vulnerablity in the MSASN1 library when decoding ** a double (real) type. If the encoding is ASCII (specified by ** not setting bit 7 or 8), and the buffer is greater than 256, ** then you overflow the array in the function. */ if(asn1->ident.tag == SF_ASN1_TAG_REAL && !asn1->ident.flag) { if(asn1->len.size && asn1->data && ((asn1->data[0] & 0xc0) == 0x00) && (asn1->len.size > 256)) { return 1; } } return 0; } /* ** NAME ** DetectDoubleOverflow:: */ /** ** This is just a wrapper to the traverse function. It's important because ** this allows us to do more with individual nodes in the future. ** ** @return integer ** ** @retval 0 failed ** @rteval 1 detected */ static int DetectDoubleOverflow(ASN1_TYPE *asn1) { return asn1_traverse(asn1, NULL, DoubleOverflow); } /* ** NAME ** OversizeLength:: */ /** ** This is the most generic of our ASN.1 detection functionalities. This ** will compare the ASN.1 type lengths against the user defined max ** length and alert if the length is greater than the user supplied length. ** ** @return integer ** ** @retval 0 failed ** @retval 1 detected */ static int OversizeLength(ASN1_TYPE *asn1, void *user) { unsigned int *max_size; if(!asn1 || !user) return 0; max_size = (unsigned int *)user; if(*max_size && *max_size <= asn1->len.size) return 1; return 0; } /* ** NAME ** DetectOversizeLength:: */ /** ** This is just a wrapper to the traverse function. It's important because ** this allows us to do more with individual nodes in the future. ** ** @return integer ** ** @retval 0 failed ** @rteval 1 detected */ static int DetectOversizeLength(ASN1_TYPE *asn1, unsigned int max_size) { return asn1_traverse(asn1, (void *)&max_size, OversizeLength); } /* ** NAME ** Asn1DetectFuncs:: */ /** ** The main function for adding ASN.1 detection type functionality. ** ** @return integer ** ** @retval 0 failed ** @retval 1 detected */ static int Asn1DetectFuncs(ASN1_TYPE *asn1, ASN1_CTXT *ctxt, int dec_ret_val) { int iRet = 0; /* ** Print first, before we do other detection. If print is the only ** option, then we want to evaluate this option as true and continue. ** Otherwise, if another option is wrong, then we */ if(ctxt->print) { asn1_traverse(asn1, NULL, asn1_print_types); iRet = 1; } /* ** Let's check the bitstring overflow. */ if(ctxt->bs_overflow) { iRet = DetectBitStringOverflow(asn1); if(iRet) return 1; } if(ctxt->double_overflow) { iRet = DetectDoubleOverflow(asn1); if(iRet) return 1; } if(ctxt->length) { iRet = DetectOversizeLength(asn1, ctxt->max_length); /* ** If we didn't detect any oversize length in the decoded structs, ** that might be because we had a really overlong length that is ** bigger than our data type could hold. In this case, it's ** overlong too. */ if(!iRet && dec_ret_val == ASN1_ERR_OVERLONG_LEN) iRet = 1; /* ** We add this return in here, so that we follow suit with the ** previous detections. Just trying to short-circuit any future ** problems if we change the code flow here. */ if(iRet) return 1; } return iRet; } /* ** NAME ** Asn1DoDetect:: */ /** ** Workhorse detection function. Does not depend on OTN. ** We check all the offsets to make sure we're in bounds, etc. ** ** @return integer ** ** @retval 0 failed ** @retval 1 detected */ int Asn1DoDetect(u_int8_t *data, u_int16_t dsize, ASN1_CTXT *ctxt, u_int8_t *rel_ptr) { ASN1_TYPE *asn1; int iRet; unsigned int size; char *start; char *end; char *offset = NULL; /* ** Failed if there is no data to decode. */ if(!data) return 0; start = data; end = start + dsize; switch(ctxt->offset_type) { case REL_OFFSET: if(!rel_ptr) { DEBUG_WRAP(DebugMessage(DEBUG_ASN1, "[*] No rel_ptr for " "relative offset, so we are bailing.\n");); return 0; } /* ** Check that it is in bounds first. */ if(!inBounds(start, end, rel_ptr)) { DEBUG_WRAP(DebugMessage(DEBUG_ASN1, "[*] ASN.1 bounds " "check failed for rel_ptr.\n");); return 0; } if(!inBounds(start, end, rel_ptr+ctxt->offset)) { DEBUG_WRAP(DebugMessage(DEBUG_ASN1, "[*] ASN.1 bounds " "check failed rel_ptr+offset.\n");); return 0; } offset = rel_ptr+ctxt->offset; break; case ABS_OFFSET: default: if(!inBounds(start, end, start+ctxt->offset)) { DEBUG_WRAP(DebugMessage(DEBUG_ASN1, "[*] ASN.1 bounds " "check failed.\n");); return 0; } offset = start+ctxt->offset; break; } /* ** Final Check. We are good to go now. */ if(!inBounds(start,end,offset)) { DEBUG_WRAP(DebugMessage(DEBUG_ASN1, "[*] ASN.1 bounds " "check failed.\n");); return 0; } /* ** Set size for asn1_decode(). This should never be -1 since ** we do the previous in bounds check. */ size = end - offset; iRet = asn1_decode(offset, size, &asn1); if(iRet && !asn1) { DEBUG_WRAP(DebugMessage(DEBUG_ASN1, "[*] ASN.1 decode failed " "miserably.\n");); return 0; } /* ** Let's do detection now. */ return Asn1DetectFuncs(asn1, ctxt, iRet); }