View Issue Details

IDProjectCategoryView StatusLast Update
0005467Kali LinuxNew Tool Requestspublic2019-09-30 13:29
Reporteraltjx Assigned To 
PrioritynormalSeveritymajorReproducibilityN/A
Status closedResolutionwon't fix 
Product Version2019.2 
Summary0005467: [Tool Submission] Leprechaun - Post-exploitation tool to help identify valuable services and systems
Description

Leprechaun is a tool created by Alton Johnson (@altonjx) to help penetration testers identify valuable resources on an internal network penetration test. For example, when obtaining domain administrator with a tool such as Bloodhound, the user is able to run recursive netstat commands on multiple hosts and use Leprechaun to aggregate the results, spitting out a nice beautiful graph to display what connections are established within the environment. Essentially, they're able to map out the data flow.

Reddit post: https://www.reddit.com/r/netsec/comments/bu2470/postexploitation_with_leprechaun_finding/
Github: https://github.com/vonahi-security/leprechaun
Blog post: https://blog.vonahi.io/post-exploitation-with-leprechaun/

I also have a previous tool that was submitted in Kali, called iSMTP, in case that helps expedite the process for me.

Thanks!

Regards,
Alton Johnson
Founder & Principal Security Consultant, Vonahi Security
[email protected]

Steps To Reproduce

Simply run the command with the following arguments: ./leprechaun.rb -p 80 -f netstat_results.txt -t internal

Attached Files
leprechuan.rb (6,721 bytes)   
just wanted to say i really appreciate being part of a good family here over the last 4 years. i'm officially moving to ATL in a little less than two weeks (planning on leaving out June 7th) to continue growing my business. i've been talking about it and wanting to for years, but now is the perfect time. with the additional of another full-time employee on my team, the pressure is on -- shit's about to get real, lol.#!/usr/bin/env ruby
#
# This tool was intended to be used during post-exploitation.
# Essentially, once you have elevated privileges, you can run a recursive netstat
# using tools such as winexe, smbexec, psexec, whatever., and parse it here.
#
# See the GitHub or blog page for more information.
#
# Author; Alton Johnson (@altonjx)
# Company: Vonahi Security (@vonahi_security)
# Created: 05/22/2019
# Version: 1.0
#

['securerandom','terminal-table','getopt/std'].each(&method(:require))
def help
	puts "\n " + "-" * 61
	puts " \e[1;34mLeprechaun v1.0 - Alton Johnson (@altonjx)\e[0;00m"
	puts " " + "-" * 61
	puts "\n  Usage: #{$0} -f /path/to/netstat_results.txt -p <port>"
	puts "\n  -f\tFile containing the output of netstat results."
	puts "  -p\tPort you're interested in. e.g., 80. Specify \"all\", \"common\", or separate ports with commas"
	puts "  -t\tThe type of destination IP addresses you want to see connections to (e.g. external/internal/all)."
	puts "\n  Example: #{$0} -f netstat_output.txt -p 80"
	puts "  Example: #{$0} -f netstat_output.txt -p all"
	puts "  Example: #{$0} -f netstat_output.txt -p common"
	puts "  Example: #{$0} -f netstat_output.txt -p 80,443 -t external"
	puts "\n"
	exit
end
PRIVATE_IPS = [
	IPAddr.new('10.0.0.0/8'),
	IPAddr.new('172.16.0.0/12'),
	IPAddr.new('192.168.0.0/16'),
].freeze
class Leprechaun
	def initialize(netstat_results, ports, ip_type)
		@servers = {}
		@clients = {}
		@dest_port_mappings = []
		@source_port_mappings = []
		@ip_type = ip_type
		@data = File.open(netstat_results).read.split("\n")
		if ports.include? ","
			@ports = ports.split(",")
		else
			@ports = ports
		end
		@digraph = "digraph {\n"
		@digraph += "\toverlap = false;\n\n"
		@digraph_headers = "\t# Servers and clients are defined here.\n"
		@digraph_data = "\t# Connections are defined here.\n"
	end
	def private_ip?(ip_address)
		if ip_address.is_a?(String)
			ip_address = IPAddr.new(ip_address)
		end
		PRIVATE_IPS.any? { |private_ip| private_ip.include?(ip_address) }
	end
	def parse_data
		@data.each do |line|
			routes = line.scan(/\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}:\d+\b/)
			next if routes.empty? or routes.nil?
			begin
				source_ip = routes[0].split(":")[0] # source IP address
				source_port = routes[0].split(":")[1] # source port
				dest_ip = routes[1].split(":")[0] # destination IP address
				dest_port = routes[1].split(":")[1] # destination port
			rescue
				next
			end
			next if dest_ip == "0.0.0.0"
			# Skip depending on type of traffic the user wants.
			if @ip_type == "internal"
				next unless private_ip? dest_ip
			elsif @ip_type == "external"
				next if private_ip? dest_ip
			end
			protocol = (line.downcase.include?("tcp") ? "tcp" : "udp")
			well_known = [17,21,22,23,25,53,69,80,81,86,110,123,135,139,143,161,389,443,445,587,636,1311,1433,1434,1720,2301,2381,3306,3389,4443,47001,5060,5061,5432,5500,5900,5901,5985,5986,7080,8080,8081,8082,8089,8000,8180,8443]

			if @ports.include? "common"
				next unless well_known.include? dest_port.to_i
			end
			if [email protected]? "all" and [email protected]? "common"
				if [email protected]? dest_port
					next
				end
			end
			if @servers[dest_ip].nil?  # avoid adding duplicate connections
				server_hex = SecureRandom.hex(2)
				@servers[dest_ip] = {:hex => "", :ports => {}, :client_count => 0}
				@servers[dest_ip][:hex] = "s#{server_hex}"
				@digraph_headers += "\t#{@servers[dest_ip][:hex]} [label = < <b>#{dest_ip}</b> >, fillcolor=gold3, fontcolor=white, style=filled, shape=egg];\n"
			end
			if @servers[dest_ip][:ports]["#{dest_port}/#{protocol}"].nil?
				@servers[dest_ip][:ports]["#{dest_port}/#{protocol}"] = {:clients => [], :client_count => 0}
			end

			unless @servers[dest_ip][:ports]["#{dest_port}/#{protocol}"][:clients].include? source_ip
				@servers[dest_ip][:ports]["#{dest_port}/#{protocol}"][:clients] << source_ip # add source IP
				@servers[dest_ip][:ports]["#{dest_port}/#{protocol}"][:client_count] += 1
				@servers[dest_ip][:client_count] += 1
			end

			if @clients[source_ip].nil? # avoid adding duplicate connections
				client_hex = SecureRandom.hex(2)
				@clients[source_ip] = "c#{client_hex}"
				@digraph_headers += "\t#{@clients[source_ip]} [label = \"#{source_ip}\", fillcolor=green3, style=filled];\n"
			end

			if @dest_port_mappings.include? [dest_ip, "#{dest_port}/#{protocol}"]
				unless @source_port_mappings.include? [source_ip, "#{dest_port}/#{protocol}"]
					@digraph_data += "\t\"#{@clients[source_ip]}\" -> \"#{dest_port}/#{protocol}\";\n"
					@source_port_mappings << [source_ip, "#{dest_port}/#{protocol}"]
				end
			else
				@dest_port_mappings << [dest_ip, "#{dest_port}/#{protocol}"]
				@source_port_mappings << [source_ip, "#{dest_port}/#{protocol}"]
				@digraph_data += "\t\"#{@clients[source_ip]}\" -> \"#{dest_port}/#{protocol}\" -> #{@servers[dest_ip][:hex]};\n"
			end
		end
		@digraph += "#{@digraph_headers}\n #{@digraph_data}"
		@digraph += "}"
	end
	def print_table
		# Most connected clients.
		headers = ['Server','Number of connected clients','Highest traffic destination port']
		data = [] # server IP address, connected clients, connected ports
		@servers.each do |ip, server_values|
			connected_clients = server_values[:client_count]
			ports = [] # port, # of connected clients
			server_values[:ports].each do |port, port_values|
				ports << [port, port_values[:client_count]]
			end
			ports.sort {|a,b| a[1] <=> b[1]}
			data << [ip, connected_clients, ports[0]]
		end
		data = data.sort {|a,b| a[1] <=> b[1]}.reverse
		table = Terminal::Table.new do |t|
			t.add_row headers
			t.add_separator
			data.each do |line|
				t.add_row [line[0], line[1], "#{line[2][0]} (#{line[2][1]} connections)"]
			end
		end
		puts table
	end
	def write_to_file
		puts "\n [*] Completed! Graph output file located at: ./network_diagram.png\n\n"
		File.open("data.dot", "w") {|f| f.write(@digraph)}
		`sfdp -Tpng data.dot -o network_diagram.png -Grankdir=LR`
	end
end
if $0 == __FILE__
	if ARGV.length == 0
		help
	end
	opt = Getopt::Std.getopts("f:p:t:")
	fail "Please specify a netstat output file (-f) as well as a port (-p)." unless opt['f'] and opt['p']
	opt['t'] = "all" if opt['t'].nil?
	lep = Leprechaun.new(opt['f'], opt['p'], opt['t'])
	lep.parse_data
	lep.write_to_file
	lep.print_table
end
leprechuan.rb (6,721 bytes)   

Activities

altjx

altjx

2019-05-29 14:15

reporter   ~0010638

Some additional information (based on what I saw in another issue):

To help speed up the process of evaluating the tool, please make sure to include the following information (the more information you include, the more beneficial it will for us):

Name: Leprechaun
Version: 1.0
GitHub: https://github.com/vonahi-security/leprechaun
Homepage: (see blog and GitHub above)
Download: (see GitHub)
License: MIT license
Description: (see original description)
Dependencies: The following ruby gems need to be installed: securerandom, terminal-table, and getopt
Similar tools: None that I know of
Activity: The project started for me years ago but I just recently published its code on May 22nd, 2019.
How to install: Simply run git clone https://github.com/vonahi-security/leprechaun, install its small 3 ruby gems, and it's good to go.
How to use: see below for some examples

  • Example: ./leprechaun.rb -f netstat_output.txt -p 80
  • Example: ./leprechaun.rb -f netstat_output.txt -p all
  • Example: ./leprechaun.rb -f netstat_output.txt -p common
  • Example: ./leprechaun.rb -f netstat_output.txt -p 80,443 -t external
g0tmi1k

g0tmi1k

2019-09-30 13:29

administrator   ~0011150

Thank you for the submission, however we haven't got the cycles to add this in

Issue History

Date Modified Username Field Change
2019-05-29 14:11 altjx New Issue
2019-05-29 14:11 altjx File Added: leprechuan.rb
2019-05-29 14:15 altjx Note Added: 0010638
2019-09-30 13:29 g0tmi1k Status new => closed
2019-09-30 13:29 g0tmi1k Resolution open => won't fix
2019-09-30 13:29 g0tmi1k Note Added: 0011150