From eb3ebba17f2626f23782739872dd09fef9ae04c6 Mon Sep 17 00:00:00 2001 From: Brendan Coles Date: Fri, 22 Feb 2019 16:02:02 +0000 Subject: [PATCH] Cleanup evasion extension; disable scramble obfuscation --- core/main/crypto.rb | 29 ++++- extensions/evasion/config.yaml | 14 ++- extensions/evasion/evasion.rb | 121 ++++++++++++------- extensions/evasion/extension.rb | 3 +- extensions/evasion/obfuscation/base_64.rb | 11 +- extensions/evasion/obfuscation/minify.rb | 25 ++-- extensions/evasion/obfuscation/scramble.rb | 6 +- extensions/evasion/obfuscation/whitespace.rb | 13 +- 8 files changed, 146 insertions(+), 76 deletions(-) diff --git a/core/main/crypto.rb b/core/main/crypto.rb index c1b87b77c..6da601f9c 100644 --- a/core/main/crypto.rb +++ b/core/main/crypto.rb @@ -46,6 +46,32 @@ module Core token end + # + # Generates a random alphanumeric string + # Note: this isn't securely random + # @todo use SecureRandom once Ruby 2.4 is EOL + # + # @param length integer length of returned string + # + def self.random_alphanum_string(length = 10) + raise TypeError, 'Invalid length' unless length.integer? + raise TypeError, 'Invalid length' unless length.positive? + + [*('a'..'z'),*('A'..'Z'),*('0'..'9')].shuffle[0,length].join + end + + # + # Generates a random hex string + # + # @param length integer length of returned string + # + def self.random_hex_string(length = 10) + raise TypeError, 'Invalid length' unless length.integer? + raise TypeError, 'Invalid length' unless length.positive? + + OpenSSL::Random.random_bytes(length).unpack('H*').first[0...length] + end + # # Generates a unique identifier for DNS rules. # @@ -53,10 +79,9 @@ module Core # def self.dns_rule_id id = nil - length = 4 begin - id = OpenSSL::Random.random_bytes(length).unpack('H*')[0] + id = random_hex_string(8) BeEF::Core::Models::Dns::Rule.each { |rule| throw StandardError if id == rule.id } rescue StandardError retry diff --git a/extensions/evasion/config.yaml b/extensions/evasion/config.yaml index a3a928c68..afbdb489e 100644 --- a/extensions/evasion/config.yaml +++ b/extensions/evasion/config.yaml @@ -9,13 +9,19 @@ beef: enable: false name: 'Evasion' authors: ["antisnatchor"] + + # Exclude code JavaScript libraries exclude_core_js: ["lib/jquery-1.12.4.min.js", "lib/json2.js", "lib/jools.min.js", "lib/mdetect.js"] - scramble_variables: true - scramble_cookies: true + + # Obfuscation methods are executed in the order in which they're provided here + # Available techniques: ["minify", "base_64", "whitespace"] + chain: ["minify", "base_64"] + + # experimental (broken - do not use): + scramble_variables: false + scramble_cookies: false scramble: beef: "beef" Beef: "Beef" evercookie: "evercookie" BeEF: "BeEF" - chain: ["scramble", "minify"] -# other available obfuscation techniques: ["minify", "base_64", "whitespace"] diff --git a/extensions/evasion/evasion.rb b/extensions/evasion/evasion.rb index 10bce1cf4..0e4032b24 100644 --- a/extensions/evasion/evasion.rb +++ b/extensions/evasion/evasion.rb @@ -6,55 +6,86 @@ module BeEF module Extension module Evasion - class Evasion - include Singleton - @@config = BeEF::Core::Configuration.instance - @@techniques = @@config.get('beef.extension.evasion.chain') + class Evasion + include Singleton - def initialize + @@config = BeEF::Core::Configuration.instance + @@enabled = @@config.get('beef.extension.evasion.enable') + + def initialize + return unless @@enabled + @techniques ||= load_techniques + + if @techniques.empty? + print_error '[Evasion] Initialization failed. No obfuscation techniques specified.' + @@config.set('beef.extension.evasion.enable', false) + return end - # Obfuscate the input JS applying the chain of techniques defined in the main config file. - def obfuscate(input) - @input = apply_chain(input, @@techniques) - end - - def add_bootstrapper - @bootstrap = '' - # add stuff at the end, only once (when serving the initial init javascript) - @@techniques.each do |technique| - #1. get the ruby module inside the obfuscation directory: the file name will be the same of the string used in "chain" - #2. call the "execute" method of the ruby module, passing the input - #3. update the input in order that next technique will work on the pre-processed input. - if File.exists?("#{$root_dir}/extensions/evasion/obfuscation/#{technique}.rb") - klass = BeEF::Extension::Evasion.const_get(technique.capitalize).instance - is_bootstrap_needed = klass.need_bootstrap - if is_bootstrap_needed - print_debug "[OBFUSCATION] Adding bootstrapper for technique [#{technique}]" - @bootstrap += klass.get_bootstrap - end - end - @bootstrap - end - @bootstrap - end - - def apply_chain(input, techniques) - @output = input - techniques.each do |technique| - #1. get the ruby module inside the obfuscation directory: the file name will be the same of the string used in "chain" - #2. call the "execute" method of the ruby module, passing the input - #3. update the input in order that next technique will work on the pre-processed input. - if File.exists?("#{$root_dir}/extensions/evasion/obfuscation/#{technique}.rb") - print_debug "[OBFUSCATION] Applying technique [#{technique}]" - klass = BeEF::Extension::Evasion.const_get(technique.capitalize).instance - @output = klass.execute(@output, @@config) - end - @output - end - @output - end + print_debug "[Evasion] Loaded obfuscation chain: #{@techniques.join(', ')}" end + + # load obfuscation technique chain + def load_techniques + techniques = @@config.get('beef.extension.evasion.chain') || [] + return [] if techniques.empty? + + chain = [] + techniques.each do |technique| + unless File.exist?("#{$root_dir}/extensions/evasion/obfuscation/#{technique}.rb") + print_error "[Evasion] Failed to load obfuscation technique '#{technique}' - file does not exist" + next + end + chain << technique + end + + chain + rescue => e + print_error "[Evasion] Failed to load obfuscation technique chain: #{e.message}" + [] + end + + # Obfuscate the input JS applying the chain of techniques defined in the main config file. + def obfuscate(input) + @input = apply_chain(input) + end + + def add_bootstrapper + bootstrap = '' + # add stuff at the end, only once (when serving the initial init javascript) + @techniques.each do |technique| + # Call the "execute" method of the technique module, passing the input and update + # the input in preperation for the next technique in the chain + klass = BeEF::Extension::Evasion.const_get(technique.capitalize).instance + if klass.need_bootstrap? + print_debug "[Evasion] Adding bootstrapper for technique: #{technique}" + bootstrap << klass.get_bootstrap + end + end + + bootstrap + rescue => e + print_error "[Evasion] Failed to bootstrap obfuscation technique: #{e.message}" + puts e.backtrace + end + + def apply_chain(input) + output = input + @techniques.each do |technique| + # Call the "execute" method of the technique module, passing the input and update + # the input in preperation for the next technique in the chain + print_debug "[Evasion] Applying technique: #{technique}" + klass = BeEF::Extension::Evasion.const_get(technique.capitalize).instance + output = klass.execute(output, @@config) + end + + print_debug "[Evasion] Obfuscation completed (#{output.length} bytes)" + output + rescue => e + print_error "[Evasion] Failed to apply obfuscation technique: #{e.message}" + puts e.backtrace + end + end end end end diff --git a/extensions/evasion/extension.rb b/extensions/evasion/extension.rb index 79c4b3135..181e8661a 100644 --- a/extensions/evasion/extension.rb +++ b/extensions/evasion/extension.rb @@ -16,8 +16,7 @@ end end require 'extensions/evasion/evasion' -require 'extensions/evasion/helper' -require 'extensions/evasion/obfuscation/scramble' +#require 'extensions/evasion/obfuscation/scramble' require 'extensions/evasion/obfuscation/minify' require 'extensions/evasion/obfuscation/base_64' require 'extensions/evasion/obfuscation/whitespace' diff --git a/extensions/evasion/obfuscation/base_64.rb b/extensions/evasion/obfuscation/base_64.rb index 2493beebb..4c3cdf3b4 100644 --- a/extensions/evasion/obfuscation/base_64.rb +++ b/extensions/evasion/obfuscation/base_64.rb @@ -9,21 +9,22 @@ module BeEF class Base_64 include Singleton - def need_bootstrap + def need_bootstrap? true end def get_bootstrap - # the decode function is obfuscated, and it's called "dec" (see below in "execute", where it is used) - decode_function = 'var _0x33db=["\x61\x74\x6F\x62","\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5A\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6A\x6B\x6C\x6D\x6E\x6F\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7A\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x2B\x2F\x3D","","\x63\x68\x61\x72\x41\x74","\x69\x6E\x64\x65\x78\x4F\x66","\x66\x72\x6F\x6D\x43\x68\x61\x72\x43\x6F\x64\x65","\x6C\x65\x6E\x67\x74\x68","\x6A\x6F\x69\x6E"];function dec(_0x487fx2){if(window[_0x33db[0]]){return atob(_0x487fx2);} ;var _0x487fx3=_0x33db[1];var _0x487fx4,_0x487fx5,_0x487fx6,_0x487fx7,_0x487fx8,_0x487fx9,_0x487fxa,_0x487fxb,_0x487fxc=0,_0x487fxd=0,dec=_0x33db[2],_0x487fxe=[];if(!_0x487fx2){return _0x487fx2;} ;_0x487fx2+=_0x33db[2];do{_0x487fx7=_0x487fx3[_0x33db[4]](_0x487fx2[_0x33db[3]](_0x487fxc++));_0x487fx8=_0x487fx3[_0x33db[4]](_0x487fx2[_0x33db[3]](_0x487fxc++));_0x487fx9=_0x487fx3[_0x33db[4]](_0x487fx2[_0x33db[3]](_0x487fxc++));_0x487fxa=_0x487fx3[_0x33db[4]](_0x487fx2[_0x33db[3]](_0x487fxc++));_0x487fxb=_0x487fx7<<18|_0x487fx8<<12|_0x487fx9<<6|_0x487fxa;_0x487fx4=_0x487fxb>>16&0xff;_0x487fx5=_0x487fxb>>8&0xff;_0x487fx6=_0x487fxb&0xff;if(_0x487fx9==64){_0x487fxe[_0x487fxd++]=String[_0x33db[5]](_0x487fx4);} else {if(_0x487fxa==64){_0x487fxe[_0x487fxd++]=String[_0x33db[5]](_0x487fx4,_0x487fx5);} else {_0x487fxe[_0x487fxd++]=String[_0x33db[5]](_0x487fx4,_0x487fx5,_0x487fx6);} ;} ;} while(_0x487fxc<_0x487fx2[_0x33db[6]]);;dec=_0x487fxe[_0x33db[7]](_0x33db[2]);return dec;};' + # the decode function is obfuscated, and it's called "dec" + # (see below in "execute", where it is used) + 'var _0x33db=["\x61\x74\x6F\x62","\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5A\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6A\x6B\x6C\x6D\x6E\x6F\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7A\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x2B\x2F\x3D","","\x63\x68\x61\x72\x41\x74","\x69\x6E\x64\x65\x78\x4F\x66","\x66\x72\x6F\x6D\x43\x68\x61\x72\x43\x6F\x64\x65","\x6C\x65\x6E\x67\x74\x68","\x6A\x6F\x69\x6E"];function dec(_0x487fx2){if(window[_0x33db[0]]){return atob(_0x487fx2);} ;var _0x487fx3=_0x33db[1];var _0x487fx4,_0x487fx5,_0x487fx6,_0x487fx7,_0x487fx8,_0x487fx9,_0x487fxa,_0x487fxb,_0x487fxc=0,_0x487fxd=0,dec=_0x33db[2],_0x487fxe=[];if(!_0x487fx2){return _0x487fx2;} ;_0x487fx2+=_0x33db[2];do{_0x487fx7=_0x487fx3[_0x33db[4]](_0x487fx2[_0x33db[3]](_0x487fxc++));_0x487fx8=_0x487fx3[_0x33db[4]](_0x487fx2[_0x33db[3]](_0x487fxc++));_0x487fx9=_0x487fx3[_0x33db[4]](_0x487fx2[_0x33db[3]](_0x487fxc++));_0x487fxa=_0x487fx3[_0x33db[4]](_0x487fx2[_0x33db[3]](_0x487fxc++));_0x487fxb=_0x487fx7<<18|_0x487fx8<<12|_0x487fx9<<6|_0x487fxa;_0x487fx4=_0x487fxb>>16&0xff;_0x487fx5=_0x487fxb>>8&0xff;_0x487fx6=_0x487fxb&0xff;if(_0x487fx9==64){_0x487fxe[_0x487fxd++]=String[_0x33db[5]](_0x487fx4);} else {if(_0x487fxa==64){_0x487fxe[_0x487fxd++]=String[_0x33db[5]](_0x487fx4,_0x487fx5);} else {_0x487fxe[_0x487fxd++]=String[_0x33db[5]](_0x487fx4,_0x487fx5,_0x487fx6);} ;} ;} while(_0x487fxc<_0x487fx2[_0x33db[6]]);;dec=_0x487fxe[_0x33db[7]](_0x33db[2]);return dec;};' end def execute(input, config) encoded = Base64.strict_encode64(input) # basically, use atob if supported otherwise a normal base64 JS implementation (ie.: IE :-) - var_name = BeEF::Extension::Evasion::Helper::random_string(3) + var_name = BeEF::Core::Crypto::random_alphanum_string(3) input = "var #{var_name}=\"#{encoded}\";[].constructor.constructor(dec(#{var_name}))();" - print_debug "[OBFUSCATION - BASE64] Javascript has been base64'ed'" + print_debug "[OBFUSCATION - Base64] Javascript has been base64 encoded" input end end diff --git a/extensions/evasion/obfuscation/minify.rb b/extensions/evasion/obfuscation/minify.rb index 63d498c3a..596f88929 100644 --- a/extensions/evasion/obfuscation/minify.rb +++ b/extensions/evasion/obfuscation/minify.rb @@ -10,19 +10,26 @@ module BeEF class Minify include Singleton - def need_bootstrap + def need_bootstrap? false end def execute(input, config) - begin - input2 = Uglifier.compile(input) - print_debug "[OBFUSCATION - MINIFIER] Javascript has been minified" - input2 - rescue - print_error "[OBFUSCATION - MINIFIER FAILED] Javascript couldn't be minified. Returning the input form." - input - end + opts = { + :output => { + :comments => :none + }, + :compress => { + :dead_code => true, + :drop_console => (config.get('beef.client_debug') ? false : true) + } + } + output = Uglifier.compile(input, opts) + print_debug "[OBFUSCATION - Minifier] JavaScript has been minified" + output + rescue => e + print_error "[OBFUSCATION - Minifier] JavaScript couldn't be minified: #{e.messsage}" + input end end end diff --git a/extensions/evasion/obfuscation/scramble.rb b/extensions/evasion/obfuscation/scramble.rb index 4ecbd21f6..bbab2d116 100644 --- a/extensions/evasion/obfuscation/scramble.rb +++ b/extensions/evasion/obfuscation/scramble.rb @@ -9,7 +9,7 @@ module BeEF class Scramble include Singleton - def need_bootstrap + def need_bootstrap? false end @@ -20,7 +20,7 @@ module BeEF to_scramble.each do |var, value| if var == value # Variables have not been scrambled yet - mod_var = BeEF::Extension::Evasion::Helper::random_string(3) + mod_var = BeEF::Core::Crypto::random_alphanum_string(3) @output.gsub!(var,mod_var) config.set("beef.extension.evasion.scramble.#{var}",mod_var) print_debug "[OBFUSCATION - SCRAMBLER] string [#{var}] scrambled -> [#{mod_var}]" @@ -34,7 +34,7 @@ module BeEF if config.get('beef.extension.evasion.scramble_cookies') # ideally this should not be static, but it's static in JS code, so fine for nowend - mod_cookie = BeEF::Extension::Evasion::Helper::random_string(5) + mod_cookie = BeEF::Core::Crypto::random_alphanum_string(5) if config.get('beef.http.hook_session_name') == "BEEFHOOK" @output.gsub!("BEEFHOOK",mod_cookie) config.set('beef.http.hook_session_name',mod_cookie) diff --git a/extensions/evasion/obfuscation/whitespace.rb b/extensions/evasion/obfuscation/whitespace.rb index ff94bfad3..ea42dfc2f 100644 --- a/extensions/evasion/obfuscation/whitespace.rb +++ b/extensions/evasion/obfuscation/whitespace.rb @@ -9,14 +9,15 @@ module BeEF class Whitespace include Singleton - def need_bootstrap + def need_bootstrap? true end def get_bootstrap - # the decode function is in plain text - called IE-spacer - because trolling is always a good idea - decode_function = + # the decode function is in plain text - called IE-spacer - because trolling is always a good idea + decode_function = "//Dirty IE6 whitespace bug hack +if (typeof IE_spacer === 'function') {} else { function IE_spacer(css_space) { var spacer = ''; for(y = 0; y < css_space.length/8; y++) @@ -35,15 +36,15 @@ function IE_spacer(css_space) { } spacer += String.fromCharCode(v); }return spacer; -}" +}}" end def execute(input, config) size = input.length encoded = encode(input) - var_name = BeEF::Extension::Evasion::Helper::random_string(3) + var_name = BeEF::Core::Crypto::random_alphanum_string(3) input = "var #{var_name}=\"#{encoded}\";[].constructor.constructor(IE_spacer(#{var_name}))();" - print_debug "[OBFUSCATION - WHITESPACE] #{size}byte of Javascript code has been Whitespaced" + print_debug "[OBFUSCATION - WHITESPACE] #{size} bytes of Javascript code has been Whitespaced" input end