/* detection functions */ int ruleCITRIX_METAFRAME_BOeval(void *p) { const uint8_t *cursor_normal = 0, *beg_of_payload, *end_of_payload; const uint8_t *cursor_extract = 0; uint32_t event_data_length, description_length, encr_data_length; SFSnortPacket *sp = (SFSnortPacket *) p; // flow:established, to_server; if (checkFlow(p, ruleCITRIX_METAFRAME_BOoptions[0]->option_u.flowFlags) > 0 ) { // content:"A|80 00 00 02|", offset 28, depth 5; if (contentMatch(p, ruleCITRIX_METAFRAME_BOoptions[1]->option_u.content, &cursor_normal) > 0) { // byte_test:size 4, value 16, operator <, offset 4, endian little; if (byteTest(p, ruleCITRIX_METAFRAME_BOoptions[2]->option_u.byte, cursor_normal) > 0) { if(getBuffer(sp, CONTENT_BUF_NORMALIZED, &beg_of_payload, &end_of_payload) <= 0) return RULE_NOMATCH; if((end_of_payload - beg_of_payload) < 0x28) return RULE_NOMATCH; // Extract Event Data Length ( 4 bytes little endian ) cursor_extract = &(beg_of_payload[0x0008]); event_data_length = (*cursor_extract++) & 0xFF; event_data_length |= ((*cursor_extract++) & 0xFF) << 8; event_data_length |= ((*cursor_extract++) & 0xFF) << 16; event_data_length |= ((*cursor_extract++) & 0xFF) << 24; // Extract Description Length ( 2 bytes little endian ) cursor_extract = &(beg_of_payload[0x0022]); description_length = (*cursor_extract++) & 0xFF; description_length |= ((*cursor_extract++) & 0xFF) << 8; // extract Encrypted Data Length ( 4 bytes little endian ) cursor_extract = &(beg_of_payload[0x00024]); encr_data_length = (*cursor_extract++) & 0xFF; encr_data_length |= ((*cursor_extract++) & 0xFF) << 8; encr_data_length |= ((*cursor_extract++) & 0xFF) << 16; encr_data_length |= ((*cursor_extract++) & 0xFF) << 24; if(description_length + encr_data_length > event_data_length) return RULE_MATCH; } } } return RULE_NOMATCH; }
/* detection functions */ int rule15327eval(void *p) { const uint8_t *cursor_normal = 0; const uint8_t *end_of_payload; uint16_t numanswers; uint16_t numqueries; uint16_t len; uint32_t total = 0; int i; SFSnortPacket *sp = (SFSnortPacket *) p; if(sp == NULL) return RULE_NOMATCH; if(sp->payload == NULL) return RULE_NOMATCH; // flow:to_client; if (checkFlow(p, rule15327options[0]->option_u.flowFlags) > 0 ) { // byte_test:size 2, value 7999, operator >, offset 2; if (byteTest(p, rule15327options[1]->option_u.byte, cursor_normal) > 0) { if(getBuffer(sp, CONTENT_BUF_NORMALIZED, &cursor_normal, &end_of_payload) <= 0) return RULE_NOMATCH; if ((cursor_normal+12) > end_of_payload) return RULE_NOMATCH; // Extract number of query and answer records numqueries = read_big_16(cursor_normal+4); numanswers = read_big_16(cursor_normal+6); cursor_normal += 12; // Iterate through variable-sized query records to skip to the answer records. for(i = 0; i < numqueries; i++) { while(cursor_normal < end_of_payload && *cursor_normal != 0 && !((*cursor_normal & 0xc0) == 0xc0)) { cursor_normal += *cursor_normal + 1; } if (cursor_normal >= end_of_payload) return RULE_NOMATCH; cursor_normal += ((*cursor_normal & 0xc0) == 0xc0) ? 2 : 1 ; cursor_normal += 2+2; } if(cursor_normal >= end_of_payload) return RULE_NOMATCH; // Iterate through answer records for(i = 0; i < numanswers; i++) { // Reset the counter used to detect the overflow for each new answer record total = 0; while(cursor_normal < end_of_payload && *cursor_normal != 0 && !((*cursor_normal & 0xc0) == 0xc0)) { cursor_normal += *cursor_normal + 1; } if(cursor_normal >= end_of_payload) return RULE_NOMATCH; cursor_normal += ((*cursor_normal & 0xc0) == 0xc0) ? 2 : 1 ; if ((cursor_normal + 1) >= end_of_payload) return RULE_NOMATCH; // Check that the record type is TXT, that is, 0x10 if (*(cursor_normal + 1) != 0x10) return RULE_NOMATCH; cursor_normal += 2+2+4; if (cursor_normal + 1 >= end_of_payload) return RULE_NOMATCH; // Extract the length field len = read_big_16_inc(cursor_normal); // Iterate through the byte[data] section and add each of the // individual string lengths together while(cursor_normal < end_of_payload && total < len) { total += *cursor_normal + 1; cursor_normal += *cursor_normal + 1; } // Alert if the sum (total) of the inidividual string lengths // exceeds the length field for the entire data section if(cursor_normal >= end_of_payload) return RULE_NOMATCH; else if(total > len) return RULE_MATCH; } } } return RULE_NOMATCH; }
/* * ruleMatch * * p: packet data structure, same as the one found in snort. * options: NULL terminated list of rule options * * Returns: * > 0 : match found * = 0 : no match found * * Predefined constants: * (see sf_snort_plugin_api.h for more values) * RULE_MATCH - if asn1 specifier is found within buffer * RULE_NOMATCH - if asn1 specifier is not found within buffer * */ int ruleMatchInternal(SFSnortPacket *p, Rule* rule, u_int32_t optIndex, u_int8_t **cursor) { u_int8_t *thisCursor = NULL, *startCursor = NULL; u_int8_t *tmpCursor = NULL; int retVal = RULE_NOMATCH; u_int32_t notFlag = 0; int thisType; ContentInfo *thisContentInfo = NULL; int startAdjust = 0; u_int32_t origFlags = 0; int32_t origOffset = 0; u_int32_t origDepth = 0; int continueLoop = 1; PCREInfo *thisPCREInfo = NULL; if (cursor) startCursor = thisCursor = *cursor; if (optIndex >= rule->numOptions || !rule->options[optIndex] ) return RULE_NOMATCH; thisType = rule->options[optIndex]->optionType; /* Do some saving off of some info for recursion purposes */ switch (thisType) { case OPTION_TYPE_CONTENT: thisContentInfo = rule->options[optIndex]->option_u.content; origFlags = thisContentInfo->flags; origDepth = thisContentInfo->depth; origOffset = thisContentInfo->offset; break; case OPTION_TYPE_PCRE: thisPCREInfo = rule->options[optIndex]->option_u.pcre; origFlags = thisPCREInfo->flags; break; default: /* Other checks should not need to check again like * PCRE & Content do. */ break; } do { switch (thisType) { case OPTION_TYPE_CONTENT: retVal = contentMatch(p, rule->options[optIndex]->option_u.content, &thisCursor); notFlag = rule->options[optIndex]->option_u.content->flags & NOT_FLAG; break; case OPTION_TYPE_PCRE: retVal = pcreMatch(p, rule->options[optIndex]->option_u.pcre, &thisCursor); notFlag = rule->options[optIndex]->option_u.pcre->flags & NOT_FLAG; break; case OPTION_TYPE_FLOWBIT: retVal = processFlowbits(p, rule->options[optIndex]->option_u.flowBit); notFlag = rule->options[optIndex]->option_u.flowBit->flags & NOT_FLAG; break; case OPTION_TYPE_BYTE_TEST: retVal = byteTest(p, rule->options[optIndex]->option_u.byte, thisCursor); notFlag = rule->options[optIndex]->option_u.byte->flags & NOT_FLAG; break; case OPTION_TYPE_BYTE_JUMP: retVal = byteJump(p, rule->options[optIndex]->option_u.byte, &thisCursor); notFlag = rule->options[optIndex]->option_u.byte->flags & NOT_FLAG; break; case OPTION_TYPE_FLOWFLAGS: retVal = checkFlow(p, rule->options[optIndex]->option_u.flowFlags); notFlag = rule->options[optIndex]->option_u.flowFlags->flags & NOT_FLAG; break; case OPTION_TYPE_ASN1: retVal = detectAsn1(p, rule->options[optIndex]->option_u.asn1, thisCursor); notFlag = rule->options[optIndex]->option_u.asn1->flags & NOT_FLAG; break; case OPTION_TYPE_CURSOR: retVal = checkCursor(p, rule->options[optIndex]->option_u.cursor, thisCursor); notFlag = rule->options[optIndex]->option_u.cursor->flags & NOT_FLAG; break; case OPTION_TYPE_SET_CURSOR: retVal = setCursor(p, rule->options[optIndex]->option_u.cursor, &thisCursor); notFlag = rule->options[optIndex]->option_u.cursor->flags & NOT_FLAG; break; case OPTION_TYPE_HDR_CHECK: retVal = checkHdrOpt(p, rule->options[optIndex]->option_u.hdrData); notFlag = rule->options[optIndex]->option_u.hdrData->flags & NOT_FLAG; break; case OPTION_TYPE_BYTE_EXTRACT: retVal = extractValue(p, rule->options[optIndex]->option_u.byteExtract, thisCursor); notFlag = rule->options[optIndex]->option_u.byteExtract->flags & NOT_FLAG; break; case OPTION_TYPE_LOOP: retVal = loopEval(p, rule->options[optIndex]->option_u.loop, &thisCursor); notFlag = rule->options[optIndex]->option_u.loop->flags & NOT_FLAG; break; case OPTION_TYPE_PREPROCESSOR: retVal = preprocOptionEval(p, rule->options[optIndex]->option_u.preprocOpt, &thisCursor); notFlag = rule->options[optIndex]->option_u.preprocOpt->flags & NOT_FLAG; break; } if ( notFlag ) { if ((retVal <= RULE_NOMATCH)) { /* Set this as a positive match -- a ! was specified. */ retVal = RULE_MATCH; } else /* Match */ { retVal = RULE_NOMATCH; } } if (retVal > RULE_NOMATCH) { /* This one matched. Depending on type, check the next one * either in a loop, or not, saving cursor temporarily. */ if (optIndex < rule->numOptions -1) /* hehe, optIndex is 0 based */ { int nestedRetVal; /* Here's where it gets tricky... */ if (thisType == OPTION_TYPE_CONTENT) { /* If this is a content option, we've found a match. * Save off the end-point of the current match. * Less the length of current match plus 1. * * This gives us the starting point to check for this * content again if subsequent options fail. That starting * point is the byte AFTER the beginning of the current * match. */ if ((origFlags & CONTENT_RELATIVE) && startCursor) { /* relative content. * need to adjust offset/depth as well */ tmpCursor = thisCursor - thisContentInfo->patternByteFormLength + thisContentInfo->incrementLength; /* Start Adjust is the difference between the old * starting point and the 'next' starting point. */ startAdjust = tmpCursor - startCursor; } else { /* non-relative content */ tmpCursor = thisCursor - thisContentInfo->patternByteFormLength + thisContentInfo->incrementLength; } } else if (thisType == OPTION_TYPE_PCRE) { /* Start next search at end of current pattern */ /* XXX: Could miss something here if part of pattern * repeats but not easy to tell with PCRE. */ tmpCursor = thisCursor; } nestedRetVal = ruleMatchInternal(p, rule, optIndex+1, &thisCursor); if (nestedRetVal == RULE_MATCH) { /* Cool, everyone after us matched, we're done with a match */ if (cursor) *cursor = thisCursor; break; } /* If Content or PCRE, look farther into the packet * for another match. */ if (((thisType == OPTION_TYPE_CONTENT) || (thisType == OPTION_TYPE_PCRE)) && !notFlag) { /* Only try to find this content again if it is a * positive match. */ /* And only if the next option is relative */ if (!isRelativeOption(rule->options[optIndex+1])) { /* Match failed, next option is not relative. * We're done. */ retVal = nestedRetVal; break; } switch (thisType) { case OPTION_TYPE_CONTENT: if (origFlags & CONTENT_RELATIVE) { if ((int32_t)(origDepth - startAdjust) < (int32_t)thisContentInfo->patternByteFormLength) { /* Adjusted depth would be less than the content we're searching for? * we're done. */ retVal = nestedRetVal; continueLoop = 0; } else { /* For contents that were already relative, adjust the offset & depth fields * from their original values. Makes it easy to determine when we'll be out * of the original bounds, relative to the original cursor. */ thisContentInfo->offset = origOffset + startAdjust; thisContentInfo->depth = origDepth - startAdjust; /* And use the original cursor that was passed in */ thisCursor = startCursor; } } else { thisContentInfo->flags |= CONTENT_RELATIVE; /* For contents that were not already relative, we simply use the adjusted * cursor. Set thisCursor to tmpCursor as calculated above */ thisCursor = tmpCursor; } break; case OPTION_TYPE_PCRE: /* Doesn't matter if it was already relative, * just make it relative anyway. */ thisPCREInfo->flags |= CONTENT_RELATIVE; /* For PCREs that were not already relative, we use the cursor * that was returned at the end of the pattern to start searching * again. */ thisCursor = tmpCursor; break; } continue; } /* Only need to search again when this is a * content option. If its not, we're done. */ if (nestedRetVal <= RULE_NOMATCH) { /* Handle the case when an error is propigated * via nestedRetVal. */ retVal = RULE_NOMATCH; } break; } else { /* Cool, nobody after us, we're done with a match */ if (cursor) *cursor = thisCursor; break; } } else { /* No match, get outta dodge */ break; } } while (continueLoop); /* Keep looping until we break or serialized content checks returns no match. */ /* Reset the flags for this content in case we added the * relative flag above. */ if (thisContentInfo) { thisContentInfo->flags = origFlags; thisContentInfo->offset = origOffset; thisContentInfo->depth = origDepth; } if (thisPCREInfo) { thisPCREInfo->flags = origFlags; } return retVal; }
/* detection functions */ int rule13887eval(void *p) { const uint8_t *cursor_normal = 0, *beg_of_payload, *end_of_payload; SFSnortPacket *sp = (SFSnortPacket *) p; uint16_t num_of_queries, num_of_answers, num_of_authorities; uint16_t comp_offset; uint16_t data_len; uint8_t seglen; uint8_t new_root_name[ROOT_NAME_BUF_LEN], prev_root_name[ROOT_NAME_BUF_LEN]; const uint8_t *rr_ptr; uint8_t root_name_len = 0; // flags uint8_t first_NS_record; // cruft uint16_t i,j; DEBUG_WRAP(uint16_t k); DEBUG_WRAP(printf("rule13887eval dns auth nameserver enter\n")); if(sp == NULL) return RULE_NOMATCH; if(sp->payload == NULL) return RULE_NOMATCH; // flow:established, to_client; if(checkFlow(p, rule13887options[0]->option_u.flowFlags) <= 0 ) return RULE_NOMATCH; DEBUG_WRAP(printf("passed flow\n")); // content:"|00 01|", offset 4, depth 2; if(contentMatch(p, rule13887options[1]->option_u.content, &cursor_normal) <= 0) return RULE_NOMATCH; DEBUG_WRAP(printf("passed content\n")); // byte_test:size 2, value 1, operator >, offset 8; if(byteTest(p, rule13887options[2]->option_u.byte, cursor_normal) <= 0) return RULE_NOMATCH; DEBUG_WRAP(printf("passed byte test\n")); // This check doesn't have to actually be here; the structure is only for the fast pattern matcher // // content:"|00 02 00 01|", depth 0, relative, fast_pattern; // if (contentMatch(p, rule13887options[3]->option_u.content, &cursor_normal) <= 0) // return RULE_MATCH; // // DEBUG_WRAP(printf("passed content\n")); if(getBuffer(sp, CONTENT_BUF_NORMALIZED, &beg_of_payload, &end_of_payload) <= 0) return RULE_NOMATCH; if(end_of_payload - beg_of_payload < 25) return RULE_NOMATCH; cursor_normal = beg_of_payload + 4; num_of_queries = *cursor_normal++ << 8; num_of_queries |= *cursor_normal++; num_of_answers = *cursor_normal++ << 8; num_of_answers |= *cursor_normal++; // Our byte_test assures us num_of_authorities is >= 2 so we have // something to compare later num_of_authorities = *cursor_normal++ << 8; num_of_authorities |= *cursor_normal++; cursor_normal += 2; // skip over count of additional RRs DEBUG_WRAP(printf("num_of_queries=%d num_of_answers=%d num_of_authorities=%d\n", num_of_queries, num_of_answers, num_of_authorities)); // Jump over the query records for(i = 0; i < num_of_queries; i++) { DEBUG_WRAP(printf("processing query %d, payload_offset=%x\n", i, cursor_normal - beg_of_payload)); while(cursor_normal < end_of_payload && *cursor_normal != 0 && *cursor_normal < 0xc0) // !((*cursor_normal & 0xc0) == 0xc0) cursor_normal += *cursor_normal + 1; if(cursor_normal >= end_of_payload) return RULE_NOMATCH; // (two bytes for pointer (2 bytes) or null byte (1 byte)) + type (2 bytes) + class (2 bytes) cursor_normal += (*cursor_normal >= 0xc0) ? 6 : 5; // (*cursor_normal & 0xc0) == 0xc0 } //if(cursor_normal >= end_of_payload) // return RULE_NOMATCH; // Jump over the answers for(i = 0; i < num_of_answers; i++) { DEBUG_WRAP(printf("processing answer %d, payload_offset=%x\n", i, cursor_normal - beg_of_payload)); while(cursor_normal < end_of_payload && *cursor_normal != 0 && *cursor_normal < 0xc0) { // performance improvement, !((*cursor_normal & 0xc0) == 0xc0) cursor_normal += *cursor_normal + 1; } if(cursor_normal + 4 > end_of_payload) return RULE_NOMATCH; // (two bytes for pointer (2 bytes) or a single null byte (1 byte)) + type (2 bytes) + class (2 bytes) + ttl (4 bytes) cursor_normal += (*cursor_normal >= 0xc0) ? 2 : 1; if(*cursor_normal++ == 0x00 && *cursor_normal++ == 0x05) { DEBUG_WRAP(printf("We found a CNAME record; let's just bail rather than figure this out for real.\n")); return RULE_NOMATCH; } cursor_normal += 6; if(cursor_normal + 2 > end_of_payload) return RULE_NOMATCH; data_len = (*cursor_normal++) << 8; data_len |= *cursor_normal++; cursor_normal += data_len; } // Now on to the meat... The Authoritative nameservers. If we // change roots, alert. first_NS_record = 1; for(i = 0; i < num_of_authorities; i++) { DEBUG_WRAP(printf("processing authority %d, payload_offset=%x\n", i, cursor_normal - beg_of_payload)); if(cursor_normal + 12 >= end_of_payload) return RULE_NOMATCH; j=0; // index into expanded root name while(cursor_normal < end_of_payload && *cursor_normal != 0 && *cursor_normal < 0xc0) { // performance improvement, !((*cursor_normal & 0xc0) == 0xc0) // copy the size and data for each segment seglen = *cursor_normal++; new_root_name[j++] = seglen; DEBUG_WRAP(printf("seglen = %d\n", seglen)); if((j + seglen < ROOT_NAME_BUF_LEN) && (cursor_normal + seglen < end_of_payload)) { DEBUG_WRAP(printf("copying bytes\n")); memcpy(&(new_root_name[j]), cursor_normal, seglen); cursor_normal += seglen; j += seglen; } else { return RULE_NOMATCH; } } // Either we're at a pointer, a null, or out of data if((cursor_normal + 1 < end_of_payload) && *cursor_normal >= 0xc0) { // pointer // performance improvement, (*cursor_normal & 0xc0) == 0xc0 // Set the pointer comp_offset = (*cursor_normal++ & 0x3F) << 8; comp_offset |= *cursor_normal++; rr_ptr = beg_of_payload + comp_offset; DEBUG_WRAP(printf("offset = %d\n", comp_offset)); while(rr_ptr < end_of_payload && *rr_ptr != 0 && *rr_ptr < 0xc0) { // performance improvement, !((*rr_ptr & 0xc0) == 0xc0) // copy the size and data for each segment seglen = *rr_ptr++; new_root_name[j++] = seglen; DEBUG_WRAP(printf("seglen = %d\n", seglen)); if((j + seglen < ROOT_NAME_BUF_LEN) && (rr_ptr + seglen < end_of_payload)) { DEBUG_WRAP(printf("copying bytes\n")); memcpy(&(new_root_name[j]), rr_ptr, seglen); rr_ptr += seglen; j += seglen; } else { return RULE_NOMATCH; } } DEBUG_WRAP(printf("root: ")); DEBUG_WRAP(for(k=0; k < j; k++) printf("%c", new_root_name[k])); DEBUG_WRAP(printf("\n")); // we only handle one level of compression if(rr_ptr >= end_of_payload || *rr_ptr >= 0xc0) { // performance improvement, ((*rr_ptr & 0xc0) == 0xc0) DEBUG_WRAP(printf("Multiple compression - Stopping\n")); return RULE_NOMATCH; } } else if((cursor_normal < end_of_payload) && (*cursor_normal == 0)) { // null (end of name string)