ronin-rb/ronin-exploits

View on GitHub
lib/ronin/exploits/mixins/has_targets.rb

Summary

Maintainability
A
35 mins
Test Coverage
# frozen_string_literal: true
#
# ronin-exploits - A Ruby library for ronin-rb that provides exploitation and
# payload crafting functionality.
#
# Copyright (c) 2007-2023 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# ronin-exploits 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-exploits 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-exploits.  If not, see <https://www.gnu.org/licenses/>.
#

require 'ronin/exploits/target'
require 'ronin/exploits/exceptions'

module Ronin
  module Exploits
    module Mixins
      #
      # Adds target information to an exploit class.
      #
      # @api public
      #
      # @since 1.0.0
      #
      module HasTargets
        class TargetError < ExploitError
        end

        class NoMatchingTarget < TargetError
        end

        class NoTargetSelected < TargetError
        end

        #
        # Adds {ClassMethods} to the including exploit class.
        #
        # @param [Class] exploit
        #   The exploit class including {HasTargets}.
        #
        # @api private
        #
        def self.included(exploit)
          exploit.extend ClassMethods
        end

        #
        # Class methods for defining targets.
        #
        module ClassMethods
          #
          # The targets for the exploit.
          #
          # @return [Array<Target>]
          #
          # @api semipublic
          #
          def targets
            @targets ||= if superclass.kind_of?(ClassMethods)
                           superclass.targets.dup
                         else
                           []
                         end
          end

          #
          # Adds a target to the exploit.
          #
          # @param [Hash{Symbol => Object}] kwargs
          #   Additional keyword arguments for {Target#initialize}.
          #
          # @option kwargs [Symbol, nil] :arch
          #   The architecture of the target.
          #
          # @option kwargs [Symbol, nil] :os
          #   The Operating System (OS) of the target.
          #
          # @option kwargs [String, nil] :os_version
          #   The Operating System (OS) version of the target.
          #
          # @option kwargs [Symbol, nil] :software
          #   The software name of the target.
          #
          # @option kwargs [String, nil] :version
          #   The software version of the target.
          #
          # @example
          #   target arch: :x86_64, os: :linux, software: 'Apache' do |t|
          #     t.foo = 0x123456
          #     t.bar = '...'
          #   end
          #
          # @api public
          #
          def target(**kwargs,&block)
            targets << Target.new(**kwargs,&block)
          end
        end

        # Currently selected exploit target.
        #
        # @return [Target, nil]
        #
        # @api public
        attr_reader :target

        #
        # Sets that {#target} if the `target:` keyword argument is giving.
        #
        # @param [Hash{Symbol => Object}, Integer, nil] target
        #   The optional target information to select.
        #
        # @param [Hash{Symbol => Object}] kwargs
        #   Additional keyword arguments.
        #
        # @api public
        #
        def initialize(target: nil, **kwargs)
          super(**kwargs)

          if target
            case target
            when Hash    then select_target(**target)
            when Integer then self.target = target
            else
              raise(ArgumentError,"target: must be either a Hash or an Integer")
            end
          end
        end

        #
        # Validates that a target was selected and all required params are set.
        #
        # @raise [Ronin::Core::Params::RequiredParam]
        #   One of the required params was not set.
        #
        # @raise [NoTargetSelected]
        #   No target was selected.
        #
        # @api semipublic
        #
        def perform_validate
          unless target
            raise(NoTargetSelected,"no target was selected")
          end

          super()
        end

        #
        # Selects the target to use in exploitation.
        #
        # @param [Symbol] arch
        #   The targeted Architecture.
        #
        # @param [Symbol] os
        #   The targeted Operating System (OS).
        #
        # @param [Symbol] os_version
        #   The targeted Operating System (OS) version.
        #
        # @param [Symbol] software
        #   The targeted software.
        #
        # @param [Symbol] version
        #   The targeted software version.
        #
        # @raise [NoMatchingTarget]
        #   No matching target could be found.
        #
        # @api semipublic
        #
        def select_target(arch: nil, os: nil, os_version: nil, software: nil, version: nil)
          targets = self.class.targets.lazy

          if arch
            targets = targets.select { |target| target.arch == arch }
          end

          if os
            targets = targets.select { |target| target.os == os }
          end

          if os_version
            targets = targets.select { |target| target.os_version == os_version }
          end

          if software
            targets = targets.select { |target| target.software == software }
          end

          if version
            targets = targets.select do |target|
              target.version == version
            end
          end

          unless (@target = targets.first)
            raise(NoMatchingTarget,"could not find any matching targets")
          end
        end

        #
        # Sets the target.
        #
        # @param [Target, Integer, nil] new_target
        #   The new target value or a target index.
        #
        # @return [Target, nil]
        #   The updated target value.
        #
        # @raise [ArgumentError]
        #   The new target value must be a {Target}, an Integer or nil.
        #
        # @raise [NoMatchingTarget]
        #   The target index was out of bounds.
        #
        def target=(new_target)
          case new_target
          when Integer
            @target = self.class.targets.fetch(new_target) do
              raise(NoMatchingTarget,"target index is out of bounds: #{new_target.inspect}")
            end
          when Target, nil
            @target = new_target
          else
            raise(ArgumentError,"target value must be a #{Target}, Integer or nil")
          end
        end

        #
        # The current targeted architecture.
        #
        # @return [Symbol, nil]
        #
        # @api public
        #
        def arch
          target.arch if target
        end

        #
        # The current targeted OS.
        #
        # @return [Symbol, nil]
        #
        # @api public
        #
        def os
          target.os if target
        end

        #
        # The current targeted OS version.
        #
        # @return [String, nil]
        #
        # @api public
        #
        def os_version
          target.os_version if target
        end

        #
        # The current targeted software.
        #
        # @return [String, nil]
        #
        # @api public
        #
        def software
          target.software if target
        end

        #
        # The current targeted software version.
        #
        # @return [String, nil]
        #
        # @api public
        #
        def version
          target.version if target
        end
      end
    end
  end
end