static void dns_get_next_question( dns_message_iterator_t* iter, dns_question_t* q, dns_name_t* name ) { // Set the name pointers and then skip it name->start_of_name = (u8*) iter->iter; name->start_of_packet = (u8*) iter->header; dns_skip_name( iter ); // Read the type and class q->question_type = dns_read_uint16( iter ); q->question_class = dns_read_uint16( iter ); }
int dns_msg_parse(u_char* msg, int len, dns_parse_fct fct, void* data) { u_char* begin = msg; u_char* p = begin + HEADER_OFFSET; u_char* end = msg + len; if(p >= end) return -1; // skip query section for(int i=0; i<dns_msg_count(begin,dns_s_qd); i++){ // query name if(dns_skip_name(&p,end) < 0) return -1; // skip query type+class if((p += 4) > end) return -1; } dns_record rr; for(int s = (int)dns_s_an; s < (int)__dns_max_sections; ++s){ for(int i=0; i<dns_msg_count(begin,(dns_section_type)s); i++){ // expand name if(dns_expand_name(&p,begin,end,(u_char*)rr.name,NS_MAXDNAME) < 0) return -1; // at least 8 bytes for type+class+ttl left? if((p + 8) > end) return -1; rr.type = dns_get_16(p); p += 2; rr.rr_class = dns_get_16(p); p += 2; rr.ttl = dns_get_32(p); p+= 4; // fetch rdata len if(p+2 > end) return -1; rr.rdata_len = *(p++) << 8; rr.rdata_len |= *(p++); rr.rdata = p; // skip rdata if((p += rr.rdata_len) > end) return -1; // call provided function if(fct && (*fct)(&rr,(dns_section_type)s,begin,end,data)) return -1; } } return 0; }
/** * Find an RR in a reply packet corresponding to our query * * @v dns DNS request * @v reply DNS reply * @ret rr DNS RR, or NULL if not found */ static union dns_rr_info * dns_find_rr ( struct dns_request *dns, const struct dns_header *reply ) { int i, cmp; const char *p = ( ( char * ) reply ) + sizeof ( struct dns_header ); union dns_rr_info *rr_info; /* Skip over the questions section */ for ( i = ntohs ( reply->qdcount ) ; i > 0 ; i-- ) { p = dns_skip_name ( p ) + sizeof ( struct dns_query_info ); } /* Process the answers section */ for ( i = ntohs ( reply->ancount ) ; i > 0 ; i-- ) { cmp = dns_name_cmp ( dns, reply, p ); p = dns_skip_name ( p ); rr_info = ( ( union dns_rr_info * ) p ); if ( cmp == 0 ) return rr_info; p += ( sizeof ( rr_info->common ) + ntohs ( rr_info->common.rdlength ) ); } return NULL; }
static int dns_parse_packet(struct dns_state *s, byte *p, unsigned int plen) { byte *end = p + plen; unsigned int i, j, len; unsigned int UNUSED x; #if 0 /* Dump the packet */ for (i=0; i<plen; i++) { if (!(i%16)) printf("%04x:", i); printf(" %02x", p[i]); if ((i%16)==15 || i==plen-1) putchar('\n'); } #endif GET32(x); /* ID and flags are ignored */ for (i=0; i<DNS_NUM_SECTIONS; i++) GET16(s->counts[i]); for (i=0; i<DNS_NUM_SECTIONS; i++) { s->sections[i] = p; for (j=0; j < s->counts[i]; j++) { p = dns_skip_name(p, end); /* Name */ if (!p) goto err; GET32(x); /* Type and class */ if (i != DNS_SEC_QUESTION) { GET32(x); /* TTL */ GET16(len); /* Length of data */ p += len; if (p > end) goto err; } } } s->sections[i] = p; return 0; err: return -1; }
static int dns_parse_rr(struct dns_state *s) { byte *p = s->sec_ptr; byte *end = s->sec_end; if (p == end) return 0; p = dns_skip_name(p, end); if (!p) goto err; GET16(s->rr_type); GET16(s->rr_class); GET32(s->rr_ttl); GET16(s->rr_len); s->rr_data = p; s->sec_ptr = p + s->rr_len; return 1; err: return -1; }
/* detection functions */ int rule15734eval(void *p) { const u_int8_t *cursor_raw = 0, *end_of_payload; SFSnortPacket *sp = (SFSnortPacket *) p; const u_int8_t *junkptr; // for getBuffer() u_int16_t num_updates; u_int16_t num_addtl_rrs; u_int16_t data_len; u_int16_t record_type; // cruft int i; if(sp == NULL) return RULE_NOMATCH; if(sp->payload == NULL) return RULE_NOMATCH; // flow:established, to_server; // if(checkFlow(p, rule15734options[0]->option_u.flowFlags) <= 0 ) // return RULE_NOMATCH; // content:"|28 00 00 01 00 01|", offset 2, depth 6, fast_pattern; if(contentMatch(p, rule15734options[1]->option_u.content, &cursor_raw) <= 0) return RULE_NOMATCH; DEBUG_WRAP(printf("passed content\n")); if(getBuffer(p, CONTENT_BUF_NORMALIZED, &junkptr, &end_of_payload) <= 0) return RULE_NOMATCH; if(cursor_raw + 25 >= end_of_payload) return RULE_NOMATCH; num_updates = *cursor_raw++ << 8; num_updates |= *cursor_raw++; DEBUG_WRAP(printf("num_updates=%d\n", num_updates)); if(num_updates == 0) return RULE_NOMATCH; num_addtl_rrs = *cursor_raw++ << 8; num_addtl_rrs |= *cursor_raw++; DEBUG_WRAP(printf("num_addtl_rrs=%d\n", num_addtl_rrs)); // Zone section (we force this to be one entry by content match) if(dns_skip_name(&cursor_raw, end_of_payload) <= 0) return RULE_NOMATCH; if(cursor_raw + 18 >= end_of_payload) return RULE_NOMATCH; DEBUG_WRAP(printf("SOA: 0x%02x%02x Class: 0x%02x%02x\n", cursor_raw[0], cursor_raw[1],cursor_raw[2],cursor_raw[3])); // Verify Type: SOA and Class: IN if(memcmp(cursor_raw, "\x00\x06\x00\x01", 4)) return RULE_NOMATCH; cursor_raw += 4; // Prerequisites section (we force this to be one entry by content match) if(dns_skip_name(&cursor_raw, end_of_payload) <= 0) return RULE_NOMATCH; if(cursor_raw + 14 >= end_of_payload) return RULE_NOMATCH; // FP reduction. Microsoft clients do ANY-ANY // Better solution is to make sure EXTERNAL_NET is set correctly // Verify Type: ANY Class: IN if(memcmp(cursor_raw, "\x00\xff\x00\x01", 4)) return RULE_NOMATCH; cursor_raw += 8; // Skip over class and type and Skip TTL data_len = *cursor_raw++ << 8; data_len |= *cursor_raw++; cursor_raw += data_len; // Updates (YAY!!) DEBUG_WRAP(printf("Checking updates\n")); for(i = 0; i < num_updates; i++) { DEBUG_WRAP(printf("Checking update %d...", i)); if(dns_skip_name(&cursor_raw, end_of_payload) <= 0) return RULE_NOMATCH; if(cursor_raw + 2 >= end_of_payload) return RULE_NOMATCH; record_type = *cursor_raw++ << 8; record_type |= *cursor_raw++; DEBUG_WRAP(printf("record_type 0x%04x\n", record_type)); // Alert if we see an update of type ANY (0x00FF) if(record_type == 0x00FF) return RULE_MATCH; if(cursor_raw + 8 >= end_of_payload) return RULE_NOMATCH; cursor_raw += 6; data_len = *cursor_raw++ << 8; data_len |= *cursor_raw++; cursor_raw += data_len; } // Currently, we don't care about the Additional RRs section, but if it turns // out we get false positives, we can add the requirement that there be no // TSIG (0x00fa) records aka unauthenticated update requests return RULE_NOMATCH; }
static int DetectBindTkeyDos(const uint8_t *cursor_normal, const uint8_t *end_of_buffer) { const uint8_t *query_name, *additional_name, *check; uint16_t flags, num_of_answers, answer_data_len, num_of_additional, additional_rr_type, additional_data_len; unsigned int i, query_name_len, additional_name_len; // check if we can read flags (2 bytes) // skip question number (2 bytes) // read answer number (2 bytes) // skip authority RR number (2 bytes) // and read additional RR number (2 bytes) if(cursor_normal + 10 > end_of_buffer) return RULE_NOMATCH; flags = read_big_16_inc(cursor_normal); // flags // // mask: // 0b1111101000001111 = 0xFA0F // ^^ ^^^ ^ // || ||| | // || ||| `- reply code (0000 = no error) // || ||`- recursion and others // || |`- truncated (0 = not truncated) // || `- authoritative // |`- opcode (0000 = standard query) // `- response (0 = query) // if((flags & 0xFA0F) != 0) return RULE_NOMATCH; // skip question number (we limit it to 1) cursor_normal += 2; // get the number of answers num_of_answers = read_big_16_inc(cursor_normal); // if num_of_answers > 5, bail if(num_of_answers > 5) return RULE_NOMATCH; // skip authority RR number cursor_normal += 2; // get the number of additional RRs num_of_additional = read_big_16_inc(cursor_normal); // if num_of_additional > 5, bail if(num_of_additional > 5) return RULE_NOMATCH; // store start of query name query_name = cursor_normal; // skip question Name (we limit to 1) if(dns_skip_name(&cursor_normal, end_of_buffer) != DNS_SUCCESS) return RULE_NOMATCH; // store size of query name query_name_len = cursor_normal - query_name; // only compare up to 255 bytes if(query_name_len > 255) return RULE_NOMATCH; // check that we can read the query type if(cursor_normal + 2 > end_of_buffer) return RULE_NOMATCH; // verify that the query type is TKEY (0x00F9) // checked "backwards" to drop out faster since first byte is usually 0x00 if((cursor_normal[1] != 0xF9) || (cursor_normal[0] != 0x00)) return RULE_NOMATCH; // skip type & class cursor_normal += 4; // go to the end of the answer section (up to 5) for(i=0; i < num_of_answers; i++) { // skip answer if(dns_skip_name(&cursor_normal, end_of_buffer) != DNS_SUCCESS) return RULE_NOMATCH; // skip type, class, TTL cursor_normal += 8; // make sure we can read the answer data length if(cursor_normal + 2 > end_of_buffer) return RULE_NOMATCH; // read the answer data length answer_data_len = read_big_16_inc(cursor_normal); // jump the answer data length check = cursor_normal + answer_data_len; // integer overflow check if(check < cursor_normal) return RULE_NOMATCH; cursor_normal = check; } // parse additional RRs (up to 5) for(i=0; i < num_of_additional; i++) { // store start of additional name additional_name = cursor_normal; // skip Additional RR Name if(dns_skip_name(&cursor_normal, end_of_buffer) != DNS_SUCCESS) return RULE_NOMATCH; // calculate size of additional RR name additional_name_len = cursor_normal - additional_name; // verify we can read Type (2 bytes) // skip class & TTL (or EDNS data) (6 bytes) // and read data length (2 bytes) if(cursor_normal + 10 > end_of_buffer) return RULE_NOMATCH; // read the additional RR type additional_rr_type = read_big_16_inc(cursor_normal); // skip class & TTL (or EDNS data) cursor_normal += 6; // read additional RR data length additional_data_len = read_big_16_inc(cursor_normal); // jump the additional RR data length check = cursor_normal + additional_data_len; // integer overflow check if(check < cursor_normal) return RULE_NOMATCH; cursor_normal = check; // verify type is NOT TKEY (1st condition of vuln) if(additional_rr_type == 0x00F9) continue; // if we skipped a pointer to the Query, then the // Additional RR Name is equal to the Query Name // (2nd condition of the vuln), alert. if(additional_name_len == 2) if((additional_name[0] == 0xC0) && (additional_name[1] == 0x0C)) return RULE_MATCH; // verify dns_skip_name skipped an Additional RR Name // with the same size as the Query Name // // (if the sizes are different, they can't be the same) if(query_name_len != additional_name_len) continue; // finally, verify the Additional RR Name is // equal to the Query Name for uncompressed names // (2nd condition of vuln), alert. if(memcmp(query_name, additional_name, query_name_len) == 0) return RULE_MATCH; } return RULE_NOMATCH; }