The complete, in-depth guide to Ruby programming — from syntax basics to advanced metaprogramming. Learn the language that powers Metasploit and countless security tools.
Ruby is a dynamic, open-source programming language created by Yukihiro "Matz" Matsumoto in 1995. It was designed with a single guiding principle: programmer happiness. Ruby's syntax is elegant, expressive, and a joy to read — it often reads like plain English.
Ruby became globally famous thanks to the Ruby on Rails web framework, which powered companies like GitHub, Shopify, Airbnb, and Basecamp. But Ruby's influence goes far beyond web development — it's also the language behind Metasploit, the world's most-used penetration testing framework.
For ethical hackers and security researchers, learning Ruby is a strategic investment. It opens the door to writing custom Metasploit modules, building security tools, and understanding the internals of one of the most important hacking frameworks ever built.
ruby -v in the terminal. We recommend Ruby 3.x for modern features.
You might wonder: "Why Ruby when Python, Bash, and Go exist?" The answer is simple: Metasploit. The most widely used exploitation framework in the world is written in Ruby. If you want to write custom modules, understand its internals, or extend its capabilities, you need Ruby.
Write custom exploits, payloads, and post-exploitation modules for Metasploit.
Ruby reads like English. Code is beautiful, concise, and expressive.
Pure OOP — even numbers and strings are objects. Clean, consistent design.
Write code that writes code. Extremely powerful for building tools and DSLs.
RubyGems gives you 170,000+ libraries for everything from HTTP to crypto.
Many pentest tools (BeEF, Ronin, WPScan) are written in Ruby.
Ruby is famous for its readability. Let's start with the classic first program — but Ruby-style.
# hello.rb — your first Ruby script
puts "Hello, Ethical Hacker!"
puts "Welcome to Ruby Programming"
Run it from the terminal:
ruby hello.rb
puts (put string) prints text with a newline. print prints without a newline. p prints with quotes (for debugging).
# Local variables (lowercase)
target = "192.168.1.100"
port = 22
is_open = true
version = 3.2
# String interpolation (double quotes only)
puts "Target: #{target} on port #{port}"
# Constants (start with uppercase)
DEFAULT_PORT = 22
# Symbols (immutable identifiers, very common in Ruby)
status = :open
| Operator | Meaning | Example |
|---|---|---|
+ | Addition | 5 + 3 = 8 |
- | Subtraction | 5 - 3 = 2 |
* | Multiplication | 5 * 3 = 15 |
/ | Division | 10 / 3 = 3 |
% | Modulus | 5 % 3 = 2 |
** | Exponent | 2 ** 8 = 256 |
== | Equal | 5 == 5 → true |
!= | Not equal | 5 != 3 → true |
&& / and | Logical AND | true && false → false |
|| / or | Logical OR | true || false → true |
! | Logical NOT | !true → false |
In Ruby, everything is an object. Even numbers, strings, and booleans have methods you can call on them. This elegant design makes Ruby remarkably consistent.
url = "https://target.com/admin"
puts url.upcase # HTTPS://TARGET.COM/ADMIN
puts url.length # 27
puts url.include?("admin") # true
puts url.split("/") # ["https:", "", "target.com", "admin"]
puts url.reverse
puts url[8..17] # target.com
# String interpolation
user = "admin"
puts "Logged in as #{user}"
ports = [21, 22, 80, 443, 3306]
ports.push(8080) # Add to end
ports.delete(21) # Remove value
ports.sort # Returns sorted array
puts ports[0] # 22
puts ports[-1] # Last element
puts ports.length # 5
puts ports.first # 22
# Iterate with each
ports.each do |port|
puts "Scanning port #{port}"
end
# Map — transform each element
doubled = ports.map { |p| p * 2 }
puts doubled.inspect
# Select — filter elements
low_ports = ports.select { |p| p < 1000 }
puts low_ports.inspect
# Modern hash syntax (symbols as keys)
target = {
ip: "192.168.1.1",
ports: [22, 80, 443],
os: "Linux",
vulnerable: true
}
puts target[:ip] # 192.168.1.1
target[:country] = "IN" # Add new key
# Iterate over hash
target.each do |key, value|
puts "#{key}: #{value}"
end
# Keys and values
puts target.keys.inspect
puts target.values.inspect
# Symbols are immutable, reusable identifiers
status = :open
status2 = :open
puts status.object_id == status2.object_id # true (same object!)
# Strings create new objects each time
a = "open"
b = "open"
puts a.object_id == b.object_id # false
# Symbols are used as hash keys and method names
config = { host: "localhost", port: 8080 }
port = 443
if port == 22
puts "SSH detected"
elsif port == 80
puts "HTTP detected"
elsif port == 443
puts "HTTPS detected"
else
puts "Unknown service"
end
# Modifier form (very Ruby!)
puts "Port is open" if port == 443
# unless = "if not"
authenticated = false
unless authenticated
puts "Access denied"
end
puts "Granting access" unless authenticated
port = 443
case port
when 22
puts "SSH"
when 80, 443
puts "Web service"
when 3306
puts "MySQL"
else
puts "Unknown"
end
# Times — run block N times
5.times do |i|
puts "Attempt #{i + 1}"
end
# Each — iterate over collection
[22, 80, 443].each do |port|
puts "Scanning #{port}"
end
# Range
(1..10).each { |n| puts n }
# While loop
i = 0
while i < 5
puts "Count: #{i}"
i += 1
end
# Upto and Downto
1.upto(5) { |n| puts n }
5.downto(1) { |n| puts n }
(1..100).each do |n|
next if n.even? # Skip evens
break if n > 20 # Stop at 20
puts n
end
def scan_port(host, port)
require 'socket'
begin
sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
sock.connect(Socket.sockaddr_in(port, host))
sock.close
true
rescue
false
end
end
if scan_port("192.168.1.1", 22)
puts "[+] Port 22 is OPEN"
end
def brute_force(target, port: 22, threads: 10, verbose: false)
puts "Attacking #{target}:#{port}" if verbose
# ... logic ...
end
brute_force("10.0.0.1")
brute_force("10.0.0.1", port: 21, verbose: true)
# The last expression is automatically returned
def add(a, b)
a + b # returned implicitly
end
puts add(5, 3) # 8
# Block with do...end (multi-line)
[1, 2, 3].each do |n|
puts n
end
# Block with { } (single-line)
[1, 2, 3].each { |n| puts n }
# Custom method that yields to a block
def with_timing
start = Time.now
yield
puts "Took #{Time.now - start} seconds"
end
with_timing do
sleep 1
end
# Proc — a block stored in a variable
greet = Proc.new { |name| puts "Hello, #{name}" }
greet.call("Hacker")
# Lambda — stricter version of proc
square = ->(x) { x ** 2 }
puts square.call(5) # 25
# Passing procs to methods
puts [1, 2, 3].map(&square).inspect # [1, 4, 9]
Ruby is a pure object-oriented language. Everything — even nil — is an object. Classes define blueprints for objects, complete with attributes (state) and methods (behavior).
class Target
# Attributes with getters and setters
attr_accessor :ip, :ports
attr_reader :os
attr_writer :vulnerable
# Constructor
def initialize(ip, os: "Unknown")
@ip = ip
@os = os
@ports = []
end
# Instance method
def add_port(port)
@ports << port
end
def to_s
"Target(#{@ip}, OS: #{@os}, Ports: #{@ports.join(', ')})"
end
end
# Create objects
t1 = Target.new("192.168.1.1", os: "Linux")
t1.add_port(22)
t1.add_port(80)
puts t1 # Target(192.168.1.1, OS: Linux, Ports: 22, 80)
class WebTarget < Target
def initialize(ip, domain)
super(ip, os: "Linux")
@domain = domain
end
def to_s
"#{super} [#{@domain}]"
end
end
web = WebTarget.new("10.0.0.1", "example.com")
puts web
class Exploit
def run # public by default
setup
execute
end
private
def setup
puts "Setting up..."
end
def execute
puts "Executing..."
end
end
Exploit.new.run
Modules are Ruby's answer to multiple inheritance. They let you share behavior across classes without the complications of traditional inheritance. This is called a mixin.
module Scannable
def scan(target)
puts "Scanning #{target}..."
end
def report
puts "Report generated by #{self.class}"
end
end
class NmapScanner
include Scannable
end
class NiktoScanner
include Scannable
end
NmapScanner.new.scan("192.168.1.1")
NiktoScanner.new.report
module Security
def self.hash_md5(data)
require 'digest'
Digest::MD5.hexdigest(data)
end
class Analyzer
def analyze(file)
puts "Analyzing #{file}"
end
end
end
puts Security.hash_md5("password")
analyzer = Security::Analyzer.new
analyzer.analyze("malware.bin")
Reading wordlists, writing scan results, parsing logs — file I/O is critical for security scripting.
# Read a file line by line
File.open("wordlist.txt", "r") do |file|
file.each_line do |line|
puts line.strip
end
end
# Read entire file
content = File.read("config.txt")
# Read all lines into array
lines = File.readlines("targets.txt", chomp: true)
# Write to a file (overwrites)
File.write("results.txt", "Scan complete\n")
# Append to a file
File.open("log.txt", "a") do |f|
f.puts "[+] Target scanned at #{Time.now}"
end
# Check if file exists
puts File.exist?("scan.txt") # true/false
# Parse JSON
require 'json'
data = JSON.parse(File.read("scan.json"))
puts data["hosts"]
# Write JSON
File.write("output.json", JSON.pretty_generate(data))
Gems are Ruby's libraries. With over 170,000 gems available, you'll find a tool for almost anything. bundler manages project dependencies.
# Install a gem globally
gem install requests
# List installed gems
gem list
# Update all gems
gem update
# Search for a gem
gem search -r http
Simple HTTP client. Great for API hacking and web scraping.
HTML/XML parser. Ruby's answer to BeautifulSoup.
SSH client library. Automate SSH operations and brute-force.
The pentesting framework itself — used as a library in scripts.
Packet crafting and sniffing library for network attacks.
Built-in crypto library. Encrypt, decrypt, hash, sign.
WHOIS lookups from Ruby scripts.
Colorful terminal output. Makes scripts and reports prettier.
Metasploit is written entirely in Ruby. Every exploit, payload, and post-exploitation module is a Ruby class. Learning Ruby lets you write your own modules and extend the framework.
require 'msf/core'
class MetasploitModule < Msf::Auxiliary
def initialize(info = {})
super(
'Name' => 'Custom SSH Scanner',
'Description' => 'Scans for open SSH ports',
'Author' => 'Hacker',
'License' => MSF_LICENSE
)
register_options([
Opt::RHOSTS.new(true),
Opt::RPORT.new(true, 'SSH port', 22)
])
end
def run
print_status("Scanning #{rhost}:#{rport}")
begin
connect
print_good("SSH is open on #{rhost}")
disconnect
rescue
print_error("Cannot connect")
end
end
end
| Type | Purpose | Example |
|---|---|---|
exploit | Vulnerability exploitation | ms17_010_eternalblue |
auxiliary | Scanning, fuzzing, sniffing | ssh_login |
payload | Code that runs on target | reverse_tcp |
encoder | Encode payloads to bypass AV | shikata_ga_nai |
post | Post-exploitation actions | hashdump |
nop | NOP generators | x64/single_byte |
/usr/share/metasploit-framework/modules/ on Kali Linux and study the structure.
require 'socket'
def scan_port(host, port, timeout: 1)
Socket.tcp(host, port, connect_timeout: timeout)
true
rescue Errno::ECONNREFUSED, SocketError
false
end
target = ARGV[0] || "127.0.0.1"
puts "[*] Scanning #{target}..."
(1..1024).each do |port|
if scan_port(target, port)
puts "[+] Port #{port} OPEN"
end
end
puts "[✓] Scan complete"
require 'socket'
target = "192.168.1.1"
threads = []
(1..1024).each do |port|
threads << Thread.new do
begin
Socket.tcp(target, port, connect_timeout: 1)
puts "[+] Port #{port} OPEN"
rescue
# closed
end
end
end
threads.each(&:join)
puts "Scan complete."
require 'net/http'
url = "http://target.com"
wordlist = "/usr/share/wordlists/dirb/common.txt"
File.foreach(wordlist) do |line|
path = line.strip
next if path.empty?
begin
uri = URI.parse("#{url}/#{path}")
response = Net::HTTP.get_response(uri)
unless response.code == "404"
puts "[+] #{response.code} — #{uri}"
end
rescue
# skip errors
end
end
require 'digest'
target_hash = "5f4dcc3b5aa765d61d8327deb882cf99" # md5("password")
File.foreach("/usr/share/wordlists/rockyou.txt") do |line|
word = line.strip
if Digest::MD5.hexdigest(word) == target_hash
puts "[+] CRACKED: #{word}"
break
end
end
require 'resolv'
domain = "example.com"
subs = %w[www mail api dev admin test ftp blog shop]
subs.each do |sub|
begin
ip = Resolv.getaddress("#{sub}.#{domain}")
puts "[+] #{sub}.#{domain} → #{ip}"
rescue Resolv::ResolvError
# not found
end
end
class Exploit
[:scan, :exploit, :report].each do |method|
define_method(method) do
puts "Running #{method}..."
end
end
end
e = Exploit.new
e.scan # Running scan...
e.exploit # Running exploit...
class FlexibleScanner
def method_missing(name, *args)
if name.to_s.start_with?("scan_")
protocol = name.to_s.sub("scan_", "")
puts "Scanning via #{protocol}..."
else
super
end
end
def respond_to_missing?(name, include_private = false)
name.to_s.start_with?("scan_") || super
end
end
s = FlexibleScanner.new
s.scan_tcp
s.scan_udp
s.scan_http
text = "Contact: admin@target.com or support@target.com"
# Extract emails
emails = text.scan(/[\w\.-]+@[\w\.-]+\.\w+/)
puts emails.inspect
# Match IP addresses
log = "Failed login from 192.168.1.45 and 10.0.0.7"
ips = log.scan(/\b(?:\d{1,3}\.){3}\d{1,3}\b/)
puts ips.inspect
# Substitution
clean = text.gsub(/@target\.com/, "@hacked.io")
puts clean
begin
# risky code
result = 10 / 0
rescue ZeroDivisionError => e
puts "Error: #{e.message}"
rescue StandardError => e
puts "General error: #{e.message}"
ensure
puts "This always runs"
end
# Custom exceptions
class ScanError < StandardError; end
begin
raise ScanError, "Target unreachable"
rescue ScanError => e
puts e.message
end
bundler — Manage gem dependencies with a Gemfile.each over for — Ruby's iterators are idiomatic.map, select, reduce are more expressive.attr_accessor — Instead of manual getters/setters.begin/rescue.freeze for constants — CONST = "value".freeze.# Gemfile
source 'https://rubygems.org'
gem 'httparty'
gem 'nokogiri'
gem 'net-ssh'
gem 'colorize'
# Install dependencies
bundle install
# Run with bundler
bundle exec ruby script.rb
Beginner-friendly, guided labs for learning ethical hacking.
Realistic vulnerable machines. Excellent for intermediate learners.
Downloadable vulnerable VMs. Run them locally in VirtualBox.
Modern vulnerable web app. Practice web exploitation legally.
Damn Vulnerable Web App. Classic for learning web attacks.
Spin up your own VMs on AWS, Azure, or GCP.
Ruby is one of the most elegant and enjoyable programming languages ever created. Its philosophy of programmer happiness shines through in every line of code. And in the security world, Ruby's importance is cemented by its role as the language of Metasploit — the framework that virtually every penetration tester uses.
Whether you want to write custom Metasploit modules, build security tools, or simply enjoy writing beautiful code, Ruby is a fantastic choice. It's beginner-friendly, yet powerful enough for advanced metaprogramming.
The journey follows a clear path:
Happy hacking — ethically. 💎