ronin-rb/ronin-post_ex

View on GitHub
lib/ronin/post_ex/system/fs.rb

Summary

Maintainability
A
3 hrs
Test Coverage
# frozen_string_literal: true
#
# ronin-post_ex - a Ruby API for Post-Exploitation.
#
# Copyright (c) 2007-2023 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 <https://www.gnu.org/licenses/>.
#

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<String>]
        #   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 directory.
        #
        # @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