[
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2019 Vonahi Security\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Leprechaun\n\n```\n                                       .-----.  \n                                      /   V   \\ \n                                      |__...__|\n                                      |_....._|\n                                    .-'  ___  '-.\n                                    \\_.-`. .`-._/\n              __ .--. _              (|\\ (_) /|)\n           .-;.-\"-.-;`_;-,            ( \\_=_/ )\n         .(_( `)-;___),-;_),          _(_   _)_\n        (.( `\\.-._)-.(   ). )       /` ||'-'|| `\\\n      ,(_`'--;.__\\  _).;--'`_)  _  /_/ (_>o<_) \\_\\\n     // )`--..__ ``` _( o )'(';,)\\_//| || : || |\\\\\n     \\;'        `````  `\\\\   '.\\\\--' |`\"\"\"\"\"\"\"`|//\n     /                   ':.___//     \\___,___/\\_(\n    |                      '---'|      |__|__|\n    ;        Leprechaun         ;      ;\"\"|\"\";\n     \\                         /       [] | []\n      '.     #vonahisec      .'      .'  / \\  '.\n        '-,.__         __.,-'        `--'   `--'\n         (___/`````````\\___) \n```\nThe purpose of this tool is to help penetration testers identify potentially valuable targets on the internal network environment. By aggregating netstat routes from multiple hosts, you can easily figure out what's going on within.\n\n## Getting Started\n\nThese instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system.\n\n### Prerequisites\n\nYou'll need a few Ruby gems to get started - if you don't have them already, that is.\n\n```\ngem install 'securerandom'\ngem install 'terminal-table'\ngem install 'getopt'\n```\n\nLastly, make sure you have Graphviz installed. You can install that with the following command:\n\n`apt install graphviz -y`\n\n### Tool help menu\n\nIf you run the script without any arguments, you'll see the following help menu:\n\n```\n[root:vonahisec-kali:~/scripts/leprechaun]# ./leprechaun.rb\n\n -------------------------------------------------------------\n Leprechaun v1.0 - Alton Johnson (@altonjx)\n -------------------------------------------------------------\n\n  Usage: ./leprechaun.rb -f /path/to/netstat_results.txt -p <port>\n\n  -f  File containing the output of netstat results\n  -p  Port you're interested in. e.g., 80. Specify \"all\", \"common\", or separate ports with commas\n  -e  The type of destination IP addresses you want to see connections to (e.g. external/internal/all)\n\n  Example: ./leprechaun.rb -f netstat_output.txt -p 80\n  Example: ./leprechaun.rb -f netstat_output.txt -p all\n  Example: ./leprechaun.rb -f netstat_output.txt -p common\n  Example: ./leprechaun.rb -f netstat_output.txt -p 80,443 -t external\n```\n\n### Example outputs\n\n```\n+--------------+-----------------------------+----------------------------------+\n| Server       | Number of connected clients | Highest traffic destination port |\n+--------------+-----------------------------+----------------------------------+\n| 192.12.70.71 | 4                           | 80/tcp (4 clients)               |\n| 192.12.70.18 | 2                           | 443/tcp (2 clients)              |\n| 192.12.70.45 | 1                           | 445/tcp (1 clients)              |\n+--------------+-----------------------------+----------------------------------+\n```\n![Leprechaun](https://blog.vonahi.io/content/images/2019/05/data_well_known-1.png)\n\n\n## Additional References\n\nBlog post: https://blog.vonahi.io/post-exploitation-with-leprechaun/\n\nLinkedIn Article: https://www.linkedin.com/pulse/finding-gaps-your-network-segmentation-using-johnson-oscp-osce/\n\n## Authors\n\n* **Alton Johnson** - *Creator* - [Twitter](https://www.twitter.com/altonjx) - [LinkedIn](https://www.linkedin.com/in/altonjx) - [GitHub](https://www.github.com/altjx)\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details\n\n## Acknowledgments & Credits\n\n* Josh Stone - Influenced by Routehunter"
  },
  {
    "path": "leprechaun.rb",
    "content": "#!/usr/bin/env ruby\n#\n# This tool was intended to be used during post-exploitation.\n# Essentially, once you have elevated privileges, you can run a recursive netstat\n# using tools such as winexe, smbexec, psexec, whatever., and parse it here.\n#\n# See the GitHub or blog page for more information.\n#\n# Author; Alton Johnson (@altonjx)\n# Company: Vonahi Security (@vonahi_security)\n# Created: Octgober 22, 2021\n# Version: 1.1\n#\n\nbegin\n  ['securerandom', 'terminal-table', 'getopt/std', 'ipaddr'].each(&method(:require))\nrescue StandardError\n  puts ' [*] Looks like a few gems are missing. Installing the gems.'\n  `gem install getopt terminal-table ipaddr`\n  puts ' [*] Finished. Please re-run this script.'\nend\ndef help\n  puts \"\\n \" + '-' * 61\n  puts \" \\e[1;34mLeprechaun v1.1 - Alton Johnson (@altonjx)\\e[0;00m\"\n  puts ' ' + '-' * 61\n  puts \"\\n  Usage: #{$0} -f /path/to/netstat_results.txt -p <port>\"\n  puts \"\\n  -f\\tFile containing the output of netstat results.\"\n  puts \"  -p\\tPort you're interested in. e.g., 80. Specify \\\"all\\\", \\\"common\\\", or separate ports with commas\"\n  puts \"  -t\\tThe type of destination IP addresses you want to see connections to (e.g. external/internal/all).\"\n  puts \"\\n  Example: #{$0} -f netstat_output.txt -p 80\"\n  puts \"  Example: #{$0} -f netstat_output.txt -p all\"\n  puts \"  Example: #{$0} -f netstat_output.txt -p common\"\n  puts \"  Example: #{$0} -f netstat_output.txt -p 80,443 -t external\"\n  puts \"\\n\"\n  exit\nend\nPRIVATE_IPS = [\n  IPAddr.new('10.0.0.0/8'),\n  IPAddr.new('172.16.0.0/12'),\n  IPAddr.new('192.168.0.0/16')\n].freeze\nclass Leprechaun\n  def initialize(netstat_results, ports, ip_type)\n    @servers = {}\n    @clients = {}\n    @dest_port_mappings = []\n    @source_port_mappings = []\n    @ip_type = ip_type\n    @data = File.open(netstat_results).read.split(\"\\n\")\n    @ports = if ports.include? ','\n               ports.split(',')\n             else\n               ports\n             end\n    @digraph = \"digraph {\\n\"\n    @digraph += \"\\toverlap = false;\\n\\n\"\n    @digraph_headers = \"\\t# Servers and clients are defined here.\\n\"\n    @digraph_data = \"\\t# Connections are defined here.\\n\"\n  end\n\n  def private_ip?(ip_address)\n    ip_address = IPAddr.new(ip_address) if ip_address.is_a?(String)\n    PRIVATE_IPS.any? { |private_ip| private_ip.include?(ip_address) }\n  end\n\n  def parse_data\n    @data.each do |line|\n      routes = line.scan(/\\b(?:[0-9]{1,3}\\.){3}[0-9]{1,3}:\\d+\\b/)\n      next if routes.empty? || routes.nil?\n\n      begin\n        source_ip = routes[0].split(':')[0] # source IP address\n        source_port = routes[0].split(':')[1] # source port\n        dest_ip = routes[1].split(':')[0] # destination IP address\n        dest_port = routes[1].split(':')[1] # destination port\n      rescue StandardError\n        next\n      end\n      next if dest_ip == '0.0.0.0'\n      next unless line.include? 'ESTABLISHED'\n\n      # Skip depending on type of traffic the user wants.\n      if @ip_type == 'internal'\n        next unless private_ip? dest_ip\n      elsif @ip_type == 'external'\n        next if private_ip? dest_ip\n      end\n      protocol = (line.downcase.include?('tcp') ? 'tcp' : 'udp')\n      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,\n                    2381, 3306, 3389, 4443, 47_001, 5060, 5061, 5432, 5500, 5900, 5901, 5985, 5986, 7080, 8080, 8081, 8082, 8089, 8000, 8180, 8443]\n      next if @ports.include?('common') && !(well_known.include? dest_port.to_i)\n      next if !@ports.include?('all') && !@ports.include?('common') && !(@ports.include? dest_port)\n\n      if @servers[dest_ip].nil? # avoid adding duplicate connections\n        server_hex = SecureRandom.hex(2)\n        @servers[dest_ip] = { hex: '', ports: {}, client_count: 0 }\n        @servers[dest_ip][:hex] = \"s#{server_hex}\"\n        @digraph_headers += \"\\t#{@servers[dest_ip][:hex]} [label = < <b>#{dest_ip}</b> >, fillcolor=gold3, fontcolor=white, style=filled, shape=egg];\\n\"\n      end\n      if @servers[dest_ip][:ports][\"#{dest_port}/#{protocol}\"].nil?\n        port_hex = SecureRandom.hex(2)\n        @servers[dest_ip][:ports][\"#{dest_port}/#{protocol}\"] = { clients: [], client_count: 0, hex: \"p#{port_hex}\" }\n        @digraph_headers += \"\\tp#{port_hex} [label = \\\"#{dest_port}/#{protocol}\\\"];\\n\"\n      end\n      unless @servers[dest_ip][:ports][\"#{dest_port}/#{protocol}\"][:clients].include? source_ip\n        @servers[dest_ip][:ports][\"#{dest_port}/#{protocol}\"][:clients] << source_ip # add source IP\n        @servers[dest_ip][:ports][\"#{dest_port}/#{protocol}\"][:client_count] += 1\n        @servers[dest_ip][:client_count] += 1\n      end\n      if @clients[source_ip].nil? # avoid adding duplicate connections\n        client_hex = SecureRandom.hex(2)\n        @clients[source_ip] = \"c#{client_hex}\"\n        @digraph_headers += \"\\t#{@clients[source_ip]} [label = \\\"#{source_ip}\\\", fillcolor=green3, style=filled];\\n\"\n      end\n      if @dest_port_mappings.include? [dest_ip, \"#{dest_port}/#{protocol}\"]\n        unless @source_port_mappings.include? [source_ip, \"#{dest_port}/#{protocol}\"]\n          @digraph_data += \"\\t#{@clients[source_ip]} -> \\\"#{@servers[dest_ip][:ports][\"#{dest_port}/#{protocol}\"][:hex]}\\\";\\n\"\n          @source_port_mappings << [source_ip, \"#{dest_port}/#{protocol}\"]\n        end\n      else\n        @dest_port_mappings << [dest_ip, \"#{dest_port}/#{protocol}\"]\n        @source_port_mappings << [source_ip, \"#{dest_port}/#{protocol}\"]\n        @digraph_data += \"\\t#{@clients[source_ip]} -> \\\"#{@servers[dest_ip][:ports][\"#{dest_port}/#{protocol}\"][:hex]}\\\" -> #{@servers[dest_ip][:hex]};\\n\"\n      end\n    end\n    @digraph += \"#{@digraph_headers}\\n #{@digraph_data}\"\n    @digraph += '}'\n  end\n\n  def print_table\n    # Most connected clients.\n    headers = ['Server', 'Number of connected clients', 'Highest traffic destination port']\n    data = [] # server IP address, connected clients, connected ports\n    @servers.each do |ip, server_values|\n      connected_clients = server_values[:client_count]\n      ports = [] # port, # of connected clients\n      server_values[:ports].each do |port, port_values|\n        ports << [port, port_values[:client_count]]\n      end\n      ports.sort { |a, b| a[1] <=> b[1] }\n      data << [ip, connected_clients, ports[0]]\n    end\n    data = data.sort { |a, b| a[1] <=> b[1] }.reverse\n    table = Terminal::Table.new do |t|\n      t.add_row headers\n      t.add_separator\n      data.each do |line|\n        t.add_row [line[0], line[1], \"#{line[2][0]} (#{line[2][1]} connections)\"]\n      end\n    end\n    puts table\n  end\n\n  def write_to_file\n    File.open('data.dot', 'w') { |f| f.write(@digraph) }\n    tmp_file = SecureRandom.hex(6)\n    `sfdp -Tpng data.dot -o leprechaun_#{tmp_file}.png -Grankdir=LR; rm data.dot`\n    puts \"\\n [*] Completed! Graph output file located at: ./leprechaun_#{tmp_file}.png\\n\\n\"\n  end\nend\nif $0 == __FILE__\n  help if ARGV.length == 0\n  opt = Getopt::Std.getopts('f:p:t:')\n  raise 'Please specify a netstat output file (-f) as well as a port (-p).' unless opt['f'] && opt['p']\n\n  opt['t'] = 'all' if opt['t'].nil?\n\n  # Check if file exists first.\n  raise 'The file provided with -f does not exist. Please select a valid file.' unless File.exist? opt['f']\n\n  lep = Leprechaun.new(opt['f'], opt['p'], opt['t'])\n  lep.parse_data\n  lep.write_to_file\n  lep.print_table\nend\n"
  }
]