xorl %eax, %eax

CVE-2010-3445: Wireshark ASN.1 BER Stack Overflow

leave a comment »

This vulnerability was discovered by The Penetration Test Team of NCNIPC as we can read in the official security advisory and it affects Wireshark 1.2 up to (and including) 1.2.11 and 1.4.0. The bug was reported on 13 September 2010 at the BugTraq mailing list as you can see here.
Here is the susceptible function which is available at epan/dissectors/packet-ber.c

int dissect_unknown_ber(packet_info *pinfo, tvbuff_t *tvb, int offset, proto_tree *tree)
{
        int start_offset;
        gint8 class;
        gboolean pc, ind;
        gint32 tag;
        guint32 len;
        int hdr_len;
        proto_item *item=NULL;
        proto_tree *next_tree=NULL;
        guint8 c;
        guint32 i;
        gboolean is_printable;
        proto_item *pi, *cause;
        asn1_ctx_t asn1_ctx;

This function is called when an unknown ASN.1 BER encoded content is encountered in a packet. The Penetration Test Team of NCNIPC triggered this through an SNMPv1 packet’s variable bindings item. Back to dissect_unknown_ber()’s code we can read the following…

/* we dont care about the class only on the constructor flag */
        switch(pc){

        case FALSE: /* this is not constructed */

          switch(class) { /* we do care about the class */
     ...
                case BER_UNI_TAG_OCTETSTRING:
                        if (decode_octetstring_as_ber) {
     ...
                                if (pc && (ber_len + (ber_offset - offset) == len)) {
                                        /* Decoded a constructed ASN.1 tag with a length indicating this
                                         * could be BER encoded data.  Try dissecting as unknown BER.
                                         */
                                        if (show_internal_ber_fields) {
     ...
                                        offset = dissect_unknown_ber(pinfo, tvb, offset, next_tree);
                                } else {

So, in case of a message with no constructor flag set, it will fall into the above ‘switch’ statement. If this is has a class of ‘BER_UNI_TAG_OCTETSTRING’ it will enter this case. In this code, if it’s able to detect a possible BER encoded data section and ‘show_internal_ber_fields’ allows it, it will recursively invoke dissect_unknown_ber().
A similar recursion also appears in the case of a set constructor flag as you can read here:

        case TRUE: /* this is constructed */
     ...
          switch(class) {
          case BER_CLASS_UNI:
            item=proto_tree_add_text(tree, tvb, offset, len, "%s", val_to_str(tag,ber_uni_tag_codes,"Unknown"));
                if(item){
                        next_tree=proto_item_add_subtree(item, ett_ber_SEQUENCE);
                }
                while(offset < (int)(start_offset + len + hdr_len))
                  offset=dissect_unknown_ber(pinfo, tvb, offset, next_tree);
                break;
     ...
        return offset;
}

The bug here is that a malicious user can construct a packet with an unknown ASN.1/BER encoded message in order to trigger this recursion. For example, if the variable bindings’ item in the SNMP PDU is set to some huge value, this function will be recursively invoked so many times that it’ll eventually consume so much stack memory’s space that will lead to a stack overflow.
In order to fix this, Wireshark’s developers developed and applied the following patch

+/*
+ * Set a limit on recursion so we don't blow away the stack. Another approach
+ * would be to remove recursion completely but then we'd exhaust CPU+memory
+ * trying to read a hellabyte of nested indefinite lengths.
+ * XXX - Max nesting in the ASN.1 plugin is 32. Should they match?
+ */
+#define BER_MAX_NESTING 500
+

So, the comment is pretty clear here. I won’t add anything else. Also, the buggy routine dissect_unknown_ber() was renamed and a new argument was added…

-int dissect_unknown_ber(packet_info *pinfo, tvbuff_t *tvb, int offset, proto_tree *tree)
+static int
+try_dissect_unknown_ber(packet_info *pinfo, tvbuff_t *tvb, int offset, proto_tree *tree, gint nest_level)
 {
 	int start_offset;

Its new argument ‘nest_level’ which is used as a recursion counter that will check during the beginning of this routine if it has passed the maximum number of iterations like this:

 	asn1_ctx_t asn1_ctx;
 
+	if (nest_level > BER_MAX_NESTING) {
+		/* Assume that we have a malformed packet. */
+		THROW(ReportedBoundsError);
+	}
+
 	start_offset=offset;

Of course, the various calls to the previous dissect_unknown_ber() were changed to match the new function and its arguments like this:

-		offset = dissect_unknown_ber(pinfo, tvb, offset, next_tree);
+		offset = try_dissect_unknown_ber(pinfo, tvb, offset, next_tree, nest_level+1);
 	      }

And for compatibility purposes, a wrapper routine named dissect_unknown_ber() was created.

+int
+dissect_unknown_ber(packet_info *pinfo, tvbuff_t *tvb, int offset, proto_tree *tree)
+{
+	return try_dissect_unknown_ber(pinfo, tvb, offset, tree, 1);
+}

The old constant ‘BER_MAX_INDEFINITE_NESTING’ was removed since the new ‘BER_MAX_NESTING’ is used now…

-/*
- * Set a limit on recursion so we don't blow away the stack. Another approach
- * would be to remove recursion completely but then we'd exhaust CPU+memory
- * trying to read a hellabyte of nested indefinite lengths.
- * XXX - Max nesting in the ASN.1 plugin is 32. Should they match?
- */
-#define BER_MAX_INDEFINITE_NESTING 500

And try_get_ber_length() was changed to use this new constant value.

 
-	if (nest_level > BER_MAX_INDEFINITE_NESTING) {
+	if (nest_level > BER_MAX_NESTING) {
 		/* Assume that we have a malformed packet. */

In the Wireshark Bug Database we can also find a PCAP capture file that can be used to trigger the vulnerability. You can download ‘ber_unknown_overflow.pcap’ here. You can make a DoS exploit code pretty fast using this capture file. Here is a more detailed representation of its contents (from the UDP datagram and below)…

----- UDP -----
SOURCE PORT:	1070 (gmrupdateserv)
DEST.  PORT:	161  (SNMP)
LENGTH     :    12049
CHECKSUM   :	0x7634 (CORRECT)

And the SNMP packet sent using the above datagram is the following…

----- SNMP -----
VERSION    :	version-1 (0)
COMMUNITY  :	PUBLIC
DATA	   :    get-request
get-request:
	     request-id  :   	4121
	     error-status:   	noError (0)
	     error-index :      0
             variable-bindings: 1 item

This item in variable bindings of the packet is simply a sequence of the following data:

0x30, 0x82, 0x2e, 0xe4, 0x06, 0x00
"a" x 12000

Wireshark recognizes the initial sequence of bytes as primitive encoded value with sequence OID of 3158140172 (which is also indicated as a malformed OID). Then, it will continue having nested applications as far as your stack can hold :P

Written by xorl

October 15, 2010 at 14:05

Posted in vulnerabilities

Leave a comment