# frozen_string_literal: true
#
# ronin-post_ex - a Ruby API for Post-Exploitation.
#
# Copyright (c) 2007-2022 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# ronin-post_ex is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ronin-post_ex is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with ronin-post_ex. If not, see .
#
require 'ronin/post_ex/resource'
require 'ronin/post_ex/remote_file'
require 'ronin/post_ex/remote_file/stat'
require 'ronin/post_ex/remote_dir'
require 'hexdump'
module Ronin
module PostEx
class System < Resource
#
# Provides access to a system's a File System (FS).
#
# # Supported API Methods
#
# The File System resource uses the following post-exploitation methods,
# defined by the {#session} object:
#
# * `fs_getcwd() -> String`
# * `fs_chdir(path : String)`
# * `fs_readlink(path : String) -> String`
# * `fs_readdir(path : String) -> Array[String]`
# * `fs_glob(pattern : String) -> Array[String]`
# * `fs_mktemp(basename : String) -> String`
# * `fs_mkdir(new_path : String)`
# * `fs_copy(src : String, dest : String)`
# * `fs_unlink(path : String)`
# * `fs_rmdir(path : String)`
# * `fs_move(src : String, dest : String)`
# * `fs_link(src : String, dest : String)`
# * `fs_chgrp(group : String, path : String)`
# * `fs_chown(user : String, path : String)`
# * `fs_chmod(mode : Integer, path : String)`
# * `fs_stat(path : String) => Hash[Symbol, Object] | nil`
#
class FS < Resource
#
# Gets the current working directory.
#
# @return [String]
# The path of the current working directory.
#
# @note
# May call the `fs_getcwd` method, if defined by the {#session}
# object.
#
def getcwd
if @session.respond_to?(:fs_getcwd)
@cwd = @session.fs_getcwd
end
return @cwd
end
resource_method :getcwd, [:fs_getcwd]
alias getwd getcwd
alias pwd getcwd
#
# Changes the current working directory.
#
# @param [String] path
# The path to use as the new current working directory.
#
# @return [String]
# The new current working directory.
#
# @note
# May call the `fs_chdir` method, if defined by the {#session}
# object.
#
def chdir(path)
path = expand_path(path)
old_cwd = @cwd
@cwd = if @session.respond_to?(:fs_chdir)
@session.fs_chdir(path)
else
path
end
if block_given?
yield @cwd
chdir(old_cwd)
end
return @cwd
end
resource_method :chdir
#
# Joins the path with the current working directory.
#
# @param [String] path
# The path to join.
#
# @return [String]
# The absolute path.
#
def expand_path(path)
if (@cwd && path[0,1] != '/')
::File.expand_path(::File.join(@cwd,path))
else
path
end
end
#
# Reads the full contents of a file.
#
# @param [String] path
# The path to the file.
#
# @return [String]
# The contents of the file.
#
# @note
# Requires the `fs_readfile` method be defined by the {#session}
# object.
#
def readfile(path)
@session.fs_readfile(path)
end
resource_method :readfile, [:fs_readfile]
#
# Reads the destination of a link.
#
# @param [String] path
# The path to the link.
#
# @return [String]
# The destination of the link.
#
# @note
# Requires the `fs_readlink` method be defined by the {#session}
# object.
#
def readlink(path)
@session.fs_readlink(path)
end
resource_method :readlink, [:fs_readlink]
#
# Opens a directory for reading.
#
# @param [String] path
# The path to the directory.
#
# @return [RemoteDir]
# The opened directory.
#
# @note
# Requires the `fs_readdir` method be defined by the {#session}
# object.
#
def readdir(path)
path = expand_path(path)
entries = @session.fs_readdir(path)
return RemoteDir.new(path,entries)
end
resource_method :readdir, [:fs_readdir]
#
# Searches the file-system for matching paths.
#
# @param [String] pattern
# A path-glob pattern.
#
# @yield [path]
# The given block, will be passed each matching path.
#
# @return [String] path
# A path in the file-system that matches the pattern.
#
# @return [Array]
# If no block is given, the matching paths will be returned.
#
# @example
# exploit.fs.glob('*.txt')
# # => [...]
#
# @example
# exploit.fs.glob('**/*.xml') do |path|
# # ...
# end
#
# @note
# Requires the `fs_glob` method be defined by the {#session} object.
#
def glob(pattern,&block)
path = expand_path(pattern)
paths = @session.fs_glob(pattern)
paths.each(&block) if block
return paths
end
resource_method :glob, [:fs_glob]
#
# Opens a file for reading.
#
# @param [String] path
# The path to file.
#
# @param [String] mode
# The mode to open the file in.
#
# @yield [file]
# If a block is given, it will be passed the newly opened file.
# After the block has returned, the file will be closed and
# `nil` will be returned.
#
# @yieldparam [RemoteFile] file
# The temporarily opened remote file.
#
# @return [RemoteFile, nil]
# If no block was given, then the newly opened remote file will be
# returned.
#
# @see RemoteFile.open
#
def open(path,mode='r',&block)
RemoteFile.open(@session,expand_path(path),mode,&block)
end
resource_method :open
#
# Hexdumps the contents of a file.
#
# @param [String] path
# The path of the file.
#
# @param [IO] output
# The output stream to write the hexdump to.
#
# @return [nil]
#
def hexdump(path,output=STDOUT)
open(path) { |file| Hexdump.dump(file,output: output) }
end
resource_method :hexdump, [:fs_read]
#
# Writes data to a file.
#
# @param [String] path
# The path to the file.
#
# @param [String] data
# The data to write.
#
# @return [nil]
#
def write(path,data)
open(path) { |file| file.write(data) }
end
resource_method :write, [:fs_write]
#
# Touches a file.
#
# @param [String] path
# The path of the file.
#
# @return [nil]
#
def touch(path)
open(path) { |file| file << '' }
end
resource_method :touch, [:fs_write]
#
# Opens a tempfile.
#
# @param [String] basename
# The base-name to use in the tempfile.
#
# @yield [tempfile]
# The given block will be passed the newly opened tempfile.
# After the block has returned, the tempfile will be closed
# and `nil` will be returned.
#
# @yieldparam [RemoteFile] tempfile
# The temporarily opened tempfile.
#
# @return [RemoteFile, nil]
# The newly opened tempfile.
#
# @note
# Requires the `fs_mktemp` method be defined by the {#session} object.
#
def tmpfile(basename,&block)
open(@session.fs_mktemp(basename),&block)
end
resource_method :tmpfile, [:fs_mktemp]
#
# Creates a directory.
#
# @param [String] path
# The path of the directory.
#
# @return [true]
# Specifies that the directory was successfully created.
#
# @note
# Requires the `fs_mkdir` method be defined by the {#session} object.
#
def mkdir(path)
@session.fs_mkdir(path)
return true
end
resource_method :mkdir, [:fs_mkdir]
#
# Copies a file.
#
# @param [String] path
# The path of the file to copy.
#
# @param [String] new_path
# The destination path to copy to.
#
# @return [true]
# Specifies that the file was successfully copied.
#
# @note
# Requires the `fs_copy` method be defined by the {#session} object.
#
def copy(path,new_path)
@session.fs_copy(expand_path(path),expand_path(new_path))
return true
end
resource_method :copy, [:fs_copy]
#
# Unlinks a file.
#
# @param [String] path
# The path of the file.
#
# @return [true]
# Specifies that the file was successfully removed.
#
# @note
# Requires the `fs_unlink` method be defined by the {#session} object.
#
def unlink(path)
@session.fs_unlink(expand_path(path))
return true
end
resource_method :unlink, [:fs_unlink]
alias rm unlink
#
# Removes a directory.
#
# @param [String] path
# The path of the directory.
#
# @return [true]
# Specifies that the directory was successfully removed.
#
# @note
# Requires the `fs_rmdir` method be defined by the {#session} object.
#
def rmdir(path)
@session.fs_rmdir(expand_path(path))
return true
end
resource_method :rmdir, [:fs_rmdir]
#
# Moves a file or directory.
#
# @param [String] path
# The path of the file or directory to be moved.
#
# @param [String] new_path
# The destination path for the file or directory to be moved to.
#
# @return [true]
# Specifies that the file or directory was successfully moved.
#
# @note
# Requires the `fs_move` method be defined by the {#session} object.
#
def move(path,new_path)
@session.fs_move(expand_path(path),expand_path(new_path))
return true
end
resource_method :move, [:fs_move]
alias rename move
#
# Creates a symbolic link.
#
# @param [String] path
# The path that the link will point to.
#
# @param [String] new_path
# The path of the link.
#
# @return [true]
# Specifies that the symbolic link was successfully created.
#
# @note
# Requires the `fs_link` method be defined by the {#session} object.
#
def link(path,new_path)
@session.fs_link(path,new_path)
return true
end
resource_method :link, [:fs_link]
#
# Changes ownership of a file or directory.
#
# @param [String, (String,String)] owner
# the user and/or group that will own the file or directory.
#
# @param [String] path
# The path of the file or directory.
#
# @return [true]
# Specifies that the ownership was successfully changed.
#
# @example
# exploit.fs.chown('www', 'one.html')
#
# @example
# exploit.fs.chown(['alice', 'users'], 'one.html')
#
# @note
# Requires the `fs_chown` method be defined by the {#session} object.
#
def chown(owner,path)
user, group = owner
chgrp(group,path) if group
@session.fs_chown(user,expand_path(path))
return true
end
resource_method :chown, [:fs_chown]
#
# Changes group ownership on one or more files or directories.
#
# @param [String] group
# The group that will own the file or directory.
#
# @param [String] path
# The path of the file or directory.
#
# @return [true]
# Specifies that the group ownership was successfully changed.
#
# @example
# exploit.fs.chgrp('www', 'one.html')
#
# @note
# Requires the `fs_chgrp` method be defined by the {#session} object.
#
def chgrp(group,path)
@session.fs_chgrp(group,expand_path(path))
return true
end
resource_method :chgrp, [:fs_chgrp]
#
# Changes permissions on one or more file or directorie.
#
# @param [Integer] mode
# The new mode for the file or directory.
#
# @param [String] path
# The path of the file or directory.
#
# @return [true]
# Specifies that the permissions were successfully changed.
#
# @example
# exploit.fs.chmod(0665, 'one.html')
#
# @note
# Requires the `fs_chmod` method be defined by the {#session} object.
#
def chmod(mode,path)
@session.fs_chmod(mode,expand_path(path))
return true
end
resource_method :chmod, [:fs_chmod]
#
# Gathers statistics on a file or directory.
#
# @param [String] path
# The path of the file or directory.
#
# @return [RemoteFile::Stat]
# The statistics on the file or directory.
#
# @see RemoteFile::Stat#initialize
#
def stat(path)
RemoteFile::Stat.new(@session,expand_path(path))
end
resource_method :stat, [:fs_stat]
#
# Tests whether a file or directory exists.
#
# @param [String] path
# The path of the file or directory in question.
#
# @return [Boolean]
# Specifies whether the file or directory exists.
#
def exists?(path)
begin
stat(path)
return true
rescue Errno::ENOENT
return false
end
end
resource_method :exists?, [:fs_stat]
#
# Tests whether a file exists.
#
# @param [String] path
# The path of the file in question.
#
# @return [Boolean]
# Specifies whether the file exists.
#
def file?(path)
begin
stat(path).file?
rescue Errno::ENOENT
return false
end
end
resource_method :file?, [:fs_stat]
#
# Tests whether a directory exists.
#
# @param [String] path
# The path of the directory in question.
#
# @return [Boolean]
# Specifies whether the directory exists.
#
def directory?(path)
begin
stat(path).directory?
rescue Errno::ENOENT
return false
end
end
resource_method :directory?, [:fs_stat]
#
# Tests whether a FIFO pipe exists.
#
# @param [String] path
# The path of the FIFO pipe in question.
#
# @return [Boolean]
# Specifies whether the FIFO pipe exists.
#
def pipe?(path)
begin
stat(path).pipe?
rescue Errno::ENOENT
return false
end
end
resource_method :pipe?, [:fs_stat]
#
# Tests whether a UNIX socket exists.
#
# @param [String] path
# The path of the UNIX socket in question.
#
# @return [Boolean]
# Specifies whether the UNIX socket exists.
#
def socket?(path)
begin
stat(path).socket?
rescue Errno::ENOENT
return false
end
end
resource_method :socket?, [:fs_stat]
#
# Tests whether a file is empty.
#
# @param [String] path
# The path of the file in question.
#
# @return [Boolean]
# Specifies whether the file is empty.
#
def zero?(path)
begin
stat(path).zero?
rescue Errno::ENOENT
return false
end
end
resource_method :zero?, [:fs_stat]
alias empty? zero?
end
end
end
end