# # Copyright (c) 2006-2025 Wade Alcorn - wade@bindshell.net # Browser Exploitation Framework (BeEF) - https://beefproject.com # See the file 'doc/COPYING' for copying permission # module BeEF module API # # Registrar class to handle all registered timed API calls # class Registrar include Singleton # # Create registrar # def initialize @registry = [] @count = 1 end # Register timed API calls to an owner # # @param [Class] owner the owner of the API hook # @param [Class] clss the API class the owner would like to hook into # @param [String] method the method of the class the owner would like to execute # @param [Array] params an array of parameters that need to be matched before the owner will be called # def register(owner, clss, method, params = []) unless verify_api_path(clss, method) print_error "API Registrar: Attempted to register non-existant API method #{clss} :#{method}" return end if registered?(owner, clss, method, params) print_debug "API Registrar: Attempting to re-register API call #{clss} :#{method}" return end id = @count @registry << { 'id' => id, 'owner' => owner, 'class' => clss, 'method' => method, 'params' => params } @count += 1 id end # # Tests whether the owner is registered for an API hook # # @param [Class] owner the owner of the API hook # @param [Class] clss the API class # @param [String] method the method of the class # @param [Array] params an array of parameters that need to be matched # # @return [Boolean] whether or not the owner is registered # def registered?(owner, clss, method, params = []) @registry.each do |r| next unless r['owner'] == owner next unless r['class'] == clss next unless r['method'] == method next unless is_matched_params? r, params return true end false end # # Match a timed API call to determine if an API.fire() is required # # @param [Class] clss the target API class # @param [String] method the method of the target API class # @param [Array] params an array of parameters that need to be matched # # @return [Boolean] whether or not the arguments match an entry in the API registry # def matched?(clss, method, params = []) @registry.each do |r| next unless r['class'] == clss next unless r['method'] == method next unless is_matched_params? r, params return true end false end # # Un-registers an API hook # # @param [Integer] id the ID of the API hook # def unregister(id) @registry.delete_if { |r| r['id'] == id } end # # Retrieves all the owners and ID's of an API hook # @param [Class] clss the target API class # @param [String] method the method of the target API class # @param [Array] params an array of parameters that need to be matched # # @return [Array] an array of hashes consisting of two keys :owner and :id # def get_owners(clss, method, params = []) owners = [] @registry.each do |r| next unless r['class'] == clss next unless r['method'] == method next unless is_matched_params? r, params owners << { owner: r['owner'], id: r['id'] } end owners end # # Verifies that the api_path has been regitered # Verifies the API path has been registered. # # @note This is a security precaution # # @param [Class] clss the target API class to verify # @param [String] mthd the target method to verify # def verify_api_path(clss, mthd) (clss.const_defined?('API_PATHS') && clss.const_get('API_PATHS').key?(mthd)) end # # Retrieves the registered symbol reference for an API hook # # @param [Class] clss the target API class to verify # @param [String] mthd the target method to verify # # @return [Symbol] the API path # def get_api_path(clss, mthd) verify_api_path(clss, mthd) ? clss.const_get('API_PATHS')[mthd] : nil end # # Matches stored API params to params # # @note If a stored API parameter has a NilClass the parameter matching is skipped for that parameter # @note By default this method returns true, this is either because the API.fire() did not include any parameters or there were no parameters defined for this registry entry # # @param [Hash] reg hash of registry element, must contain 'params' key # @param [Array] params array of parameters to be compared to the stored parameters # # @return [Boolean] whether params matches the stored API parameters # def is_matched_params?(reg, params) stored = reg['params'] return true unless stored.length == params.length stored.each_index do |i| next if stored[i].nil? return false unless stored[i] == params[i] end true end # # Fires all owners registered to this API hook # # @param [Class] clss the target API class # @param [String] mthd the target API method # @param [Array] *args parameters passed for the API call # # @return [Hash, NilClass] returns either a Hash of :api_id and :data # if the owners return data, otherwise NilClass # def fire(clss, mthd, *args) mods = get_owners(clss, mthd, args) return nil unless mods.length.positive? unless verify_api_path(clss, mthd) && clss.ancestors.first.to_s.start_with?('BeEF::API') print_error "API Path not defined for Class: #{clss} method: #{mthd}" return [] end data = [] method = get_api_path(clss, mthd) mods.each do |mod| # Only used for API Development (very verbose) # print_info "API: #{mod} fired #{method}" result = mod[:owner].method(method).call(*args) data << { api_id: mod[:id], data: result } unless result.nil? rescue StandardError => e print_error "API Fire Error: #{e.message} in #{mod}.#{method}()" end data end end end end require 'core/api/module' require 'core/api/modules' require 'core/api/extension' require 'core/api/extensions' require 'core/api/main/migration' require 'core/api/main/network_stack/assethandler' require 'core/api/main/server' require 'core/api/main/server/hook' require 'core/api/main/configuration'