CVE: 2009-0692 – ISC-dhclient stack overflow   Leave a comment

  • Post Info:
    1. # Author: Flavio do Carmo Junior aka waKKu
      # URL: Author’s Webpage
      # Date: February 06, 2011
      # Category: Exploiting, Programming, Security, Vulnerability Analysis

    Wassup fellas…

    During H2HC 6th Edition (2009) I went through the COSEINC Linux Exploitation training, taught by Rodrigo Rubira Branco a.k.a. BSDaemon. On the last day Rodrigo launched a “challenge” that could give us an “especial” certificate.
    It was November/2009 and the month’s headline vulnerability was a classic stack overflow in “netmask” field of isc-dhclient.

    I’ll try to disclose all details in the path until the flaw, the flaw itself and at last: my exploit ;).

    So, before start posting a lot of dhclient’s source code here, I’ll try to explain the process within DHCP protocol:
    1– The client (in our case dhclient process) sends a broadcast packet: DHCPDISCOVER
    2– DHCP server answers with a DHCPOFFER packet offering an IP address, netmask, lease-time and other configs
    3– So the client sends a DHCPREQUEST for that offered IP, notifying an acceptance for that configuration
    4– DHCP server then sends a DHCPACK packet, meaning “configuration saved” and finishing the process.

    A– The client now calculates, based on that lease-time previously provided by server, and schedule to send a new DHCPREQUEST packet (also known as Renewing Process) before lease’s expiration time, notifying the server that the client still holds this IP and avoiding the server to deliver it to another client.
    B– Again DHCP server sends a DHCPACK packet to confirm.
    If DHCP server configuration has changed between step 2 and B (e.g.: dns servers, new domain, router, whatever…), the server should attach these new options to DHCPACK packet.

    If we pay attention in this process we realize a “gap” to consider: Until step 4 the client HAS NO IP address! It leaves us with 2 attacking vectors:
    1) Break-in the first 4 steps and utilize a non-network shellcode;
    2) Break-in between step 4 and B and to depend on client’s renew interval.
    I was using an academic setup and had control of both (client and server machines), so decided to go with option 2 and manipulate server’s lease-time.

    After all this intro we still need to understand the flaw, let’s take a look at DHCP’s internal structures:

    #define DHCP_CHADDR_LEN    16
    #define SERVERNAME_LEN     64
    #define BOOTFILE_LEN       128
    
    struct dhcp_hdr {
    	uint8_t op;
    	uint8_t htype;
    	uint8_t hlen;
    	uint8_t hops;
    	uint32_t xid;
    	uint16_t secs;
    	uint16_t flags;
    	uint32_t ciaddr;
    	uint32_t yiaddr;
    	uint32_t siaddr;
    	uint32_t giaddr;
    	uint8_t chaddr[DHCP_CHADDR_LEN];
    	uint8_t servername[SERVERNAME_LEN];
    	uint8_t bootfile[BOOTFILE_LEN];
    	uint32_t cookie;
    } __attribute__((__packed__));
    
    struct dhcp_opt {
    	uint8_t opt;
    	uint8_t len;
    } __attribute__((__packed__));
    

    First of all we can see two structs… One for header dhcp_hdr and another for options dhcp_opt (those configurations provided by server to client goes here). This is the setup I used on my exploit, based on RFC’s and the names can probably be different from those into isc-dhcp package. If you want to understand all fields, read RFCs 2131 and 2132.

    Ok, no we’re ready to see the flaw:
    From RFC 2132 (DHCP Options):

    3.3. Subnet Mask

    The subnet mask option specifies the client’s subnet mask as per RFC
    950 [5].

    If both the subnet mask and the router option are specified in a DHCP
    reply, the subnet mask option MUST be first.

    The code for the subnet mask option is 1, and its length is 4 octets.

    Code Len Subnet Mask
    +-----+-----+-----+-----+-----+-----+
    | 1 | 4 | m1 | m2 | m3 | m4 |
    +-----+-----+-----+-----+-----+-----+

    Let’s check that vulnerable function in dhclient.c:

    dhcp-3.0.6/client/dhclient.c:script_write_params()
    
    void script_write_params (client, prefix, lease)
    	struct client_state *client;
    	const char *prefix;
    	struct client_lease *lease;
    {
    	int i;
    	struct data_string data;
    	struct option_cache *oc;
    	pair *hash;
    	char *s, *t;
    	struct envadd_state es;
    
    	es.client = client;
    	es.prefix = prefix;
    
    	client_envadd (client,
    		       prefix, "ip_address", "%s", piaddr (lease -> address));
    
    	/* For the benefit of Linux (and operating systems which may
    	   have similar needs), compute the network address based on
    	   the supplied ip address and netmask, if provided.  Also
    	   compute the broadcast address (the host address all ones
    	   broadcast address, not the host address all zeroes
    	   broadcast address). */
    
    	memset (&data, 0, sizeof data);
    	oc = lookup_option (&dhcp_universe, lease -> options, DHO_SUBNET_MASK);
    	if (oc && evaluate_option_cache (&data, (struct packet *)0,
    					 (struct lease *)0, client,
    					 (struct option_state *)0,
    					 lease -> options,
    					 &global_scope, oc, MDL)) {
    		if (data.len > 3) {
    			struct iaddr netmask, subnet, broadcast;
    
    			memcpy (netmask.iabuf, data.data, data.len);
    			netmask.len = data.len;
    			data_string_forget (&data, MDL);
    
    			subnet = subnet_number (lease -> address, netmask);
    			if (subnet.len) {
    			    client_envadd (client, prefix, "network_number",
    					   "%s", piaddr (subnet));
    
    			    oc = lookup_option (&dhcp_universe,
    						lease -> options,
    						DHO_BROADCAST_ADDRESS);
    			    if (!oc ||
    				!(evaluate_option_cache
    				  (&data, (struct packet *)0,
    				   (struct lease *)0, client,
    				   (struct option_state *)0,
    				   lease -> options,
    				   &global_scope, oc, MDL))) {
    				broadcast = broadcast_addr (subnet, netmask);
    				if (broadcast.len) {
    				    client_envadd (client,
    						   prefix, "broadcast_address",
    						   "%s", piaddr (broadcast));
    				}
    			    }
    			}
    		}
    		data_string_forget (&data, MDL);
    	}
    
    	if (lease -> filename)
    		client_envadd (client,
    			       prefix, "filename", "%s", lease -> filename);
    	if (lease -> server_name)
    		client_envadd (client, prefix, "server_name",
    			       "%s", lease -> server_name);
    
    	for (i = 0; i < lease -> options -> universe_count; i++) {
    		option_space_foreach ((struct packet *)0, (struct lease *)0,
    				      client, (struct option_state *)0,
    				      lease -> options, &global_scope,
    				      universes [i],
    				      &es, client_option_envadd);
    	}
    	client_envadd (client, prefix, "expiry", "%d", (int)(lease -> expiry));
    }
    

    Let’s struggle with this piece by piece ;):

    	memset (&data, 0, sizeof data);
    

    Here we can see variable data being filled with zeros.

    	oc = lookup_option (&dhcp_universe, lease -> options, DHO_SUBNET_MASK);
    

    Then we have a function checking if packet has options set to DHO_SUBNET_MASK (value “1” according to RFC). The result (true/false) goes into “oc”.

    	if (oc && evaluate_option_cache (&data, (struct packet *)0,
    					 (struct lease *)0, client,
    					 (struct option_state *)0,
    					 lease -> options,
    					 &global_scope, oc, MDL)) {
    

    If “oc” is true, then evaluate_option_cache() copy the contents of the server packet to variable data.

    		if (data.len > 3) {
    

    Here lies the problem… RFC says The code for the subnet mask option is 1, and its length is 4 octets. IS 4 OCTETS, not more and not less ;/. Why don’t you put data.len == 4 huh?

    			struct iaddr netmask, subnet, broadcast;
    
    			memcpy (netmask.iabuf, data.data, data.len);
    

    And here lies the consequences. memcpy() is using an user controlled value as length to copying bytes into netmask.iabuf, a limited (16 bytes) stack buffer:

    dhcp-3.0.6/includes/inet.h:
    
    struct iaddr {
    	unsigned len;
    	unsigned char iabuf [16];
    };
    

    Yeah… It finish our flaw analysis. Just in case, if you still don’t saw the problem and its exploitation, it’s simple:
    — Pretend to be a DHCP server in some network and send, answering a DHCPREQUEST, a packet with following DHCP Options:
    — – Option code: 1 (Subnet mask – RFC 2132)
    — – Subnet Mask: Your payload here.
    — – Len(gth): Size of your payload.

    Here is my thoughts written in C code:
    Video: Youtube
    Exploit: CVE-2009-0692 @ Pastebin.com

    You should notice some “details” into this exploit (we call it FixUps), the reason is that function script_write_params() still have some work to do after we messed up with its stack, so we need to make sure the function works correctly and reaches its return() instruction, there is where we steals the execution flow.

    That’s all folks!

    waKKu

    Advertisements

    Posted February 6, 2011 by waKKu in Exploiting, Programming, Security, Vulnerability Analysis

    Leave a Reply

    Fill in your details below or click an icon to log in:

    WordPress.com Logo

    You are commenting using your WordPress.com account. Log Out / Change )

    Twitter picture

    You are commenting using your Twitter account. Log Out / Change )

    Facebook photo

    You are commenting using your Facebook account. Log Out / Change )

    Google+ photo

    You are commenting using your Google+ account. Log Out / Change )

    Connecting to %s

    %d bloggers like this: