├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bin ├── bnat-handshake ├── bnat-pcap ├── bnat-router ├── bnat-scan └── bnat-simulator ├── bnat.gemspec ├── lib ├── bnat.rb ├── bnat │ ├── capture_factory.rb │ ├── common.rb │ ├── firewall.rb │ ├── packet_factory.rb │ ├── result.rb │ ├── scanner.rb │ ├── tcp_packet.rb │ └── version.rb └── ext │ └── packetfu │ └── utils.rb └── spec ├── bnat ├── capture_factory_spec.rb ├── common_spec.rb ├── packet_factory_spec.rb ├── result_spec.rb ├── scanner_spec.rb └── tcp_packet_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | *.rvmrc 4 | .bundle 5 | .config 6 | .yardoc 7 | Gemfile.lock 8 | InstalledFiles 9 | _yardoc 10 | coverage 11 | doc/ 12 | lib/bundler/man 13 | pkg 14 | rdoc 15 | spec/reports 16 | test/tmp 17 | test/version_tmp 18 | tmp 19 | *.swn 20 | *.swp 21 | *.swo 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | before_install: 3 | - sudo apt-get install libpcap-dev -qq 4 | rvm: 5 | - 2.1.1 6 | - 2.0.0 -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to BNAT 2 | 3 | Thanks for your interest in contributing to BNAT. 4 | 5 | If you could follow the following guidelines, you will make it much easier for 6 | us to give feedback, help you find whatever problem you have and fix it. 7 | 8 | ## Issues 9 | 10 | If you have questions of any kind, or are unsure of how something works, please 11 | [create an issue](https://github.com/claudijd/bnat/issues/new). 12 | 13 | Please try to answer the following questions in your issue: 14 | 15 | - What did you do? 16 | - What did you expect to happen? 17 | - What happened instead? 18 | 19 | If you have identified a bug, it would be very helpful if you could include a 20 | way to replicate the bug. Ideally a failing test would be perfect, but even a 21 | simple script demonstrating the error would suffice. 22 | 23 | Feature requests are great and if submitted they will be considered for 24 | inclusion, but sending a pull request is much more awesome. 25 | 26 | ## Pull Requests 27 | 28 | If you want your pull requests to be accepted, please follow the following guidelines: 29 | 30 | - [**Add tests!**](http://rspec.info/) Your patch won't be accepted (or will be delayed) if it doesn't have tests. 31 | 32 | - [**Document any change in behaviour**](http://yardoc.org/) Make sure the README and any other 33 | relevant documentation are kept up-to-date. 34 | 35 | - [**Create topic branches**](https://github.com/dchelimsky/rspec/wiki/Topic-Branches) Don't ask us to pull from your master branch. 36 | 37 | - [**One pull request per feature**](https://help.github.com/articles/using-pull-requests) If you want to do more than one thing, send 38 | multiple pull requests. 39 | 40 | - [**Send coherent history**](http://stackoverflow.com/questions/6934752/git-combining-multiple-commits-before-pushing) Make sure each individual commit in your pull 41 | request is meaningful. If you had to make multiple intermediate commits while 42 | developing, please squash them before sending them to us. 43 | 44 | - [**Follow coding conventions**](https://github.com/styleguide/ruby) The standard Ruby stuff, two spaces indent, 45 | don't omit parens unless you have a good reason. 46 | 47 | Thank you so much for contributing! 48 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify gem's dependencies in bnat.gemspec 4 | gemspec -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Jonathan Claudius 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of Jonathan Claudius nor the 13 | names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY JONTHAN CLAUDIUS ''AS IS'' AND ANY 17 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL JONATHAN CLAUDIUS BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BNAT 2 | 3 | [![Build Status](https://secure.travis-ci.org/claudijd/bnat.png)](http://travis-ci.org/claudijd/bnat) 4 | [![Dependency Status](https://gemnasium.com/claudijd/bnat.png)](https://gemnasium.com/claudijd/bnat) 5 | [![Code Climate](https://codeclimate.com/github/claudijd/bnat.png)](https://codeclimate.com/github/claudijd/bnat) 6 | 7 | BNAT (Broken NAT) is namely defined as IP communication that is being improperly nat'd to create an inoperable communication channel. A common example of BNAT is found in asymmetric routing where we (intentionally or unintentionally) create a logical layer 3 loop in a TCP/IP session between a client and a server. This is commonly found in complex routing scenarios or situations where mistakes are "corrected" to make something work without understanding or caring about the actual flow of traffic. 8 | 9 | ## Very Basic Example 10 | 11 | ``` 12 | .1 ----SYN-----> .2 (.1 is the client and starts a session w/ a syn to .2) 13 | .1 <--SYN/ACK--- .3 (.3 responds to .1 with the syn/ack) 14 | .1 ----RST-----> .3 (.1 responds to .3 with a rst) 15 | ``` 16 | 17 | ## Why Does BNAT Matter? 18 | 19 | BNAT effectively hides TCP ports from being identified by modern TCP clients and port scanning utilities like NMAP. With the right tools, you can identify ports that would otherwise be considered as closed/filtered which can be converted into legitimate open ports. This opens the door for traditional exploitation vectors in a service that was previously unreachable. 20 | 21 | ## BNAT Presentations 22 | 23 | - [**BNAT Hijacking: Repairing Broken Communication Channels**](https://speakerdeck.com/claudijd/bnat-hijacking-repairing-broken-communication-channels) 24 | 25 | ## BNAT Videos 26 | 27 | - [**BNAT-Scan compared to NMAP -sS Scan**](http://www.youtube.com/watch?v=8Um1cJswCeM) 28 | - [**BNAT-Router handling BNAT'd SSH Session**](http://www.youtube.com/watch?v=C8zv10VHyUg) 29 | - [**Using Metaploit's BNAT auxmod's to exploit Tomcat**](http://www.youtube.com/watch?v=FS_cg1PVhkI) 30 | 31 | ## Blog Posts 32 | 33 | - [**Metasploit Blog - A Tale From Defcon and the Fun of BNAT**](https://community.rapid7.com/community/metasploit/blog/2011/08/26/a-tale-from-defcon-and-the-fun-of-bnat) 34 | - [**Spiderlabs Blog - Advanced BNAT in the Wild**](http://blog.spiderlabs.com/2011/09/advanced-bnat-broken-network-address-translation-in-the-wild.html) 35 | 36 | ## Setup 37 | 38 | TODO 39 | 40 | ## Rubies Supported 41 | 42 | TODO 43 | 44 | ## Contributing 45 | 46 | If you are interested in contributing to this project, please see [CONTRIBUTING.md](https://github.com/claudijd/bnat/blob/master/CONTRIBUTING.md) 47 | 48 | ## Credits 49 | 50 | TODO 51 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require 'rubygems' 3 | require 'rake' 4 | require 'rubygems/package_task' 5 | require 'rspec' 6 | require 'rspec/core' 7 | require 'rspec/core/rake_task' 8 | 9 | $:.unshift File.join(File.dirname(__FILE__), "lib") 10 | 11 | require 'bnat' 12 | 13 | task :default => :spec 14 | 15 | desc "Run all specs in spec directory" 16 | RSpec::Core::RakeTask.new(:spec) 17 | 18 | def clean_up 19 | Dir.glob("*.gem").each { |f| File.unlink(f) } 20 | Dir.glob("*.lock").each { |f| File.unlink(f) } 21 | end 22 | 23 | desc "Build the gem" 24 | task :build do 25 | puts "[+] Building BNAT version #{BNAT::VERSION}" 26 | puts `gem build bnat.gemspec` 27 | end 28 | 29 | desc "Publish the gem" 30 | task :publish do 31 | puts "[+] Publishing BNAT version #{BNAT::VERSION}" 32 | Dir.glob("*.gem").each { |f| puts `gem push #{f}`} 33 | end 34 | 35 | desc "Tag the release" 36 | task :tag do 37 | puts "[+] Tagging BNAT version #{BNAT::VERSION}" 38 | `git tag #{BNAT::VERSION}` 39 | `git push --tags` 40 | end 41 | 42 | desc "Bump the Gemspec Version" 43 | task :bump do 44 | puts "[+] Bumping BNAT version #{BNAT::VERSION}" 45 | `git commit -a -m "Bumped Gem version to #{BNAT::VERSION}"` 46 | `git push origin master` 47 | end 48 | 49 | desc "Perform an end-to-end release of the gem" 50 | task :release do 51 | clean_up() # Clean up before we start 52 | Rake::Task[:build].execute 53 | Rake::Task[:bump].execute 54 | Rake::Task[:tag].execute 55 | Rake::Task[:publish].execute 56 | clean_up() # Clean up after we complete 57 | end 58 | -------------------------------------------------------------------------------- /bin/bnat-handshake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') 4 | 5 | require 'optparse' 6 | require 'ostruct' 7 | require 'bnat' 8 | 9 | options = OpenStruct.new 10 | options.target = "" 11 | options.port = 0 12 | options.interface = "eth0" 13 | 14 | OptionParser.new do |opts| 15 | opts.banner = "Usage: rvmsudo ruby bnat-handshake.rb [options]" 16 | 17 | opts.on("-t", "--target [TARGET]", "The IP of the system to test") do |t| 18 | options.target = t 19 | end 20 | 21 | opts.on("-p", "--port [PORT]", "The port to test") do |p| 22 | options.port = p 23 | end 24 | 25 | opts.on("-i", "--interface [INTERFACE]", "The interface to scan from") do |i| 26 | options.interface = i 27 | end 28 | 29 | opts.on_tail("-h", "--help", "Show this message") do 30 | puts "" 31 | puts opts 32 | puts "" 33 | puts "Example: rvmsudo ruby bnat-handshake.rb -t 192.168.1.1 -p 80" 34 | puts "" 35 | exit 36 | end 37 | end.parse! 38 | 39 | cf = Bnat::CaptureFactory.new(options.interface) 40 | pf = Bnat::PacketFactory.new(options.interface) 41 | 42 | bpf = "tcp and tcp[13] == 18" 43 | pcap = cf.get_capture(bpf) 44 | 45 | syn_pkt = pf.get_syn_probe(:ip => options.target, :port => options.port) 46 | syn_pkt.recalc 47 | syn_pkt.to_w 48 | puts "sent the syn" 49 | 50 | listen = Thread.new do 51 | loop do 52 | pcap.stream.each do |pkt| 53 | syn_ack_pkt = PacketFu::Packet.parse(pkt) 54 | 55 | puts "got the syn/ack" 56 | 57 | ack_pkt = pf.get_ack_from_syn_ack(syn_ack_pkt) 58 | ack_pkt.recalc 59 | ack_pkt.to_w 60 | 61 | puts "sent the ack" 62 | end 63 | end 64 | end 65 | 66 | listen.join 67 | -------------------------------------------------------------------------------- /bin/bnat-pcap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'rubygems' 4 | require 'packetfu' 5 | 6 | synack_hash = Hash.new 7 | synackarray = Array.new 8 | syn_hash = Hash.new 9 | synarray = Array.new 10 | 11 | puts "\nbnat-pcap.rb v0.3\n" 12 | 13 | def usage 14 | puts "\nUsage: ruby ./bnat-pcap.rb .pcap\n\n" 15 | exit 16 | end 17 | 18 | if ARGV.length != 1 or not File.exist?(ARGV[0]) 19 | usage() 20 | end 21 | 22 | sample = ARGV[0] 23 | 24 | beginning = Time.now 25 | 26 | puts "\nParsing SYN/ACK data from PCAP..." 27 | system("tcpdump -nn -r #{sample} -w ./synack.pcap tcp[13] == 18") 28 | puts "Parsed SYN/ACK data from PCAP in #{Time.now - beginning} seconds" 29 | last = Time.now 30 | 31 | pcap = PacketFu::PcapFile.new.f2a( 32 | :f => "./synack.pcap", 33 | :filter => "tcp and tcp[13] == 18" 34 | ) 35 | 36 | puts "SYN/ACK PCAP Load time was #{Time.now - last} seconds" 37 | last = Time.now 38 | 39 | pcap.each do |pkt| 40 | packet = PacketFu::Packet.parse(pkt) 41 | synack_hash = { 42 | "ip" => packet.ip_saddr.to_s, 43 | "sport" => packet.tcp_sport.to_s, 44 | "seq" => packet.tcp_ack.to_s, 45 | "dport" => packet.tcp_dport.to_s 46 | } 47 | synackarray.push(synack_hash) 48 | end 49 | 50 | synackarray = synackarray.uniq 51 | 52 | #Build BPF so we can focus on just SYN's that have a matching seq 53 | bpf = "" 54 | temp = "" 55 | temp2 = 0 56 | synackarray.each do |synack| 57 | temp = synack["seq"] 58 | temp2 = temp.to_i-1 59 | if bpf == "" 60 | bpf = "'tcp[4:4] == 0x"+temp2.to_s(16)+"'" 61 | else 62 | bpf += " or 'tcp[4:4] == 0x"+temp2.to_s(16)+"'" 63 | end 64 | end 65 | 66 | #Build BPF so we can focus on just SYN's that have a matching src port 67 | bpf2 = "" 68 | synackarray.each do |synack| 69 | temp3 = synack["dport"].to_i 70 | if bpf2 == "" 71 | bpf2 = "'tcp[0:2] == 0x"+temp3.to_s(16)+"'" 72 | else 73 | bpf2 += " or 'tcp[0:2] == 0x"+temp3.to_s(16)+"'" 74 | end 75 | end 76 | 77 | #Mark our SYN/ACK PCAP Process Start 78 | puts "SYN/ACK PCAP Process time was #{Time.now - last} seconds" 79 | last = Time.now 80 | 81 | #Scrap our SYN's that have matching SEQ's to our SYN/ACK array 82 | puts "\nParsing SYN data from PCAP based on SYN/ACK SEQ matches..." 83 | system("tcpdump -nn -r #{sample} -w ./syn.pcap 'tcp[13] == 2' and #{bpf}") 84 | 85 | #Scrap our SYN's that have matching SRC ports to our SYN/ACK array 86 | puts "Parsing SYN data from PCAP based on SYN/ACK src port matches..." 87 | system("tcpdump -nn -r #{sample} -w ./syn2.pcap 'tcp[13] == 2' and #{bpf2}") 88 | 89 | #Mark our SYN/ACK PCAP Process Completion 90 | puts "Parsed SYN data from PCAP in #{Time.now - last} seconds" 91 | last = Time.now 92 | 93 | #Load the pcaps of SYN's we scraped out 94 | pcap2 = PacketFu::PcapFile.new.f2a(:f => './syn.pcap') 95 | pcap3 = PacketFu::PcapFile.new.f2a(:f => './syn2.pcap') 96 | 97 | #Combine and de-dup our SYN's to speed up process time later 98 | pcap4 = (pcap2 + pcap3).uniq 99 | 100 | #Mark our SYN PCAP load completion 101 | puts "SYN PCAP Load time was #{Time.now - last} seconds" 102 | last = Time.now 103 | 104 | #For Each SYN, index it into a usable hash for comparison 105 | pcap4.each do |pkt| 106 | packet = PacketFu::Packet.parse(pkt) 107 | syn_hash = { 108 | "ip" => packet.ip_daddr.to_s, 109 | "dport" => packet.tcp_dport.to_s, 110 | "seq" => packet.tcp_seq.to_s, 111 | "sport" => packet.tcp_sport.to_s 112 | } 113 | synarray.push(syn_hash) 114 | end 115 | 116 | #Mark our process time 117 | puts "SYN PCAP Process time was #{Time.now - last} seconds\n\n" 118 | last = Time.now 119 | 120 | #synarray = synarray.uniq 121 | 122 | #Loop through each SYN/ACK and compare to each SYN 123 | synackarray.each do |synackpacket| 124 | synarray.each do |synpacket| 125 | temp = synpacket["seq"] 126 | temp2 = temp.to_i 127 | tempseq = temp2+1 128 | 129 | ip, dport, sport, seq = false, false, false, false 130 | 131 | ip = true if synackpacket["ip"] == synpacket["ip"] 132 | dport = true if synackpacket["dport"] == synpacket["sport"] 133 | sport = true if synackpacket["sport"] == synpacket["dport"] 134 | seq = true if synackpacket["seq"] == tempseq.to_s 135 | 136 | if !ip and dport and sport and seq 137 | puts "IP Based BNAT Detected:" 138 | puts "Request: #{synpacket}" 139 | puts "Response: #{synackpacket}" 140 | end 141 | 142 | if ip and dport and !sport and seq 143 | puts "Source Port Based BNAT Detected:" 144 | puts "Request: #{synpacket}" 145 | puts "Response: #{synackpacket}" 146 | end 147 | 148 | if ip and dport and sport and !seq 149 | puts "Sequence Number Based BNAT Detected:" 150 | puts "Request: #{synpacket}" 151 | puts "Response: #{synackpacket}" 152 | end 153 | 154 | if !ip and dport and !sport and seq 155 | puts "IP and Source Port Based BNAT Detected:" 156 | puts "Request: #{synpacket}" 157 | puts "Response: #{synackpacket}" 158 | end 159 | end 160 | end 161 | 162 | #Mark our end to end process time 163 | puts "\nTime elapsed #{Time.now - beginning} seconds\n\n" 164 | 165 | system("rm syn.pcap syn2.pcap synack.pcap") 166 | -------------------------------------------------------------------------------- /bin/bnat-router: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'rubygems' 4 | require 'packetfu' 5 | 6 | puts "\nbnat-router v0.1\n\n" 7 | 8 | inint = ARGV[0] 9 | outint = ARGV[2] 10 | clientip = ARGV[1] 11 | serverip = ARGV[3] 12 | bnatip = ARGV[4] 13 | 14 | def usage 15 | puts "\nUsage: ruby bnat-router.rb " 16 | puts "Example: ruby bnat-router.rb eth1 192.168.3.2 eth0 1.1.2.1 1.1.2.2\n\n" 17 | Process.exit 18 | end 19 | 20 | if ARGV.length != 5 21 | usage 22 | end 23 | 24 | def arp(target_ip,int) 25 | $config = PacketFu::Config.new(PacketFu::Utils.ifconfig ":#{int}").config 26 | arp_pkt = PacketFu::ARPPacket.new(:flavor => "Windows") 27 | arp_pkt.eth_saddr = arp_pkt.arp_saddr_mac = $config[:eth_saddr] 28 | arp_pkt.eth_daddr = "ff:ff:ff:ff:ff:ff" 29 | arp_pkt.arp_daddr_mac = "00:00:00:00:00:00" 30 | arp_pkt.arp_saddr_ip = $config[:ip_saddr] 31 | arp_pkt.arp_daddr_ip = target_ip 32 | cap = PacketFu::Capture.new( 33 | :iface => $config[:iface], 34 | :start => true, 35 | :filter => "arp src #{target_ip} and ether dst #{arp_pkt.eth_saddr}" 36 | ) 37 | injarp = PacketFu::Inject.new( 38 | :iface => $config[:iface] 39 | ) 40 | injarp.a2w( 41 | :array => [arp_pkt.to_s] 42 | ) 43 | target_mac = nil 44 | while target_mac.nil? 45 | if cap.save > 0 46 | arp_response = PacketFu::Packet.parse(cap.array[0]) 47 | target_mac = arp_response.arp_saddr_mac if arp_response.arp_saddr_ip = target_ip 48 | end 49 | sleep 0.1 # Check for a response ten times per second. 50 | end 51 | #puts "#{target_ip} is-at #{target_mac}" 52 | return target_mac 53 | end 54 | 55 | clientmac = arp(clientip,inint) 56 | puts "Obtained Client MAC: #{clientmac}" 57 | servermac = arp(serverip,outint) 58 | puts "Obtained Server MAC: #{servermac}" 59 | bnatmac = arp(bnatip,outint) 60 | puts "Obtained BNAT MAC: #{bnatmac}\n\n" 61 | 62 | #Create Interface Specific Configs 63 | outconfig = PacketFu::Config.new(PacketFu::Utils.ifconfig ":#{outint}").config 64 | inconfig = PacketFu::Config.new(PacketFu::Utils.ifconfig ":#{inint}").config 65 | 66 | #Set Captures for Traffic coming from Outside and from Inside respectively 67 | outpcap = PacketFu::Capture.new( 68 | :config => "#{outint}", 69 | :start => true, 70 | :filter => "tcp and src #{bnatip}" 71 | ) 72 | puts "Now listening on #{outint}..." 73 | inpcap = PacketFu::Capture.new( 74 | :iface => "#{inint}", 75 | :start => true, 76 | :filter => "tcp and src #{clientip} and dst #{serverip}" 77 | ) 78 | puts "Now listening on #{inint}...\n\n" 79 | 80 | #Start Thread from Outside Processing 81 | fromout=Thread.new do 82 | loop do 83 | outpcap.stream.each do |pkt| 84 | packet = PacketFu::Packet.parse(pkt) 85 | 86 | #Build a shell packet that will never hit the wire as a hack 87 | shell_pkt = PacketFu::TCPPacket.new( 88 | :config=>inconfig, 89 | :timeout=> 0.1, 90 | :flavor=>"Windows" 91 | ) 92 | shell_pkt.ip_daddr=clientip 93 | shell_pkt.recalc 94 | 95 | #Mangle Received Packet and Drop on the Wire 96 | packet.ip_saddr=serverip 97 | packet.ip_daddr=clientip 98 | packet.eth_saddr=shell_pkt.eth_saddr 99 | packet.eth_daddr=clientmac 100 | packet.recalc 101 | inj = PacketFu::Inject.new( 102 | :iface => "#{inint}", 103 | :config =>inconfig 104 | ) 105 | inj.a2w( 106 | :array => [packet.to_s] 107 | ) 108 | puts "inpacket processed" 109 | end 110 | end 111 | end 112 | 113 | #Start Thread from Inside Processing 114 | fromin=Thread.new do 115 | loop do 116 | inpcap.stream.each do |pkt| 117 | packet = PacketFu::Packet.parse(pkt) 118 | 119 | if packet.tcp_flags.syn == 1 and 120 | packet.tcp_flags.ack == 0 121 | 122 | packet.ip_daddr=serverip 123 | packet.eth_daddr=servermac 124 | else 125 | packet.ip_daddr=bnatip 126 | packet.eth_daddr=bnatmac 127 | end 128 | 129 | #Build a shell packet that will never hit the 130 | #wire as a hack to get desired mac's 131 | shell_pkt = PacketFu::TCPPacket.new( 132 | :config=>outconfig, 133 | :timeout=> 0.1, 134 | :flavor=>"Windows" 135 | ) 136 | shell_pkt.ip_daddr=serverip 137 | shell_pkt.recalc 138 | 139 | #Mangle Received Packet and Drop on the Wire 140 | packet.eth_saddr=shell_pkt.eth_saddr 141 | packet.recalc 142 | inj = PacketFu::Inject.new( 143 | :iface => "#{outint}", 144 | :config =>outconfig 145 | ) 146 | inj.a2w(:array => [packet.to_s]) 147 | 148 | #Double tap that SYN 149 | if packet.tcp_flags.syn == 1 and 150 | packet.tcp_flags.ack == 0 151 | 152 | sleep 0.75 153 | inj.a2w(:array => [packet.to_s]) 154 | end 155 | 156 | puts "outpacket processed" 157 | end 158 | end 159 | end 160 | 161 | #Hold Process Open 162 | fromout.join 163 | fromin.join 164 | -------------------------------------------------------------------------------- /bin/bnat-scan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') 4 | 5 | require 'optparse' 6 | require 'ostruct' 7 | require 'bnat' 8 | 9 | unless Process.uid == 0 10 | puts 'You are not running as root, you need these rights for low-level packet control' 11 | exit 12 | end 13 | 14 | options = OpenStruct.new 15 | options.targets = [] 16 | options.ports = [] 17 | options.interface = PacketFu::Utils.default_int 18 | options.fast = false 19 | 20 | OptionParser.new do |opts| 21 | opts.banner = "Usage: rvmsudo ruby bnat_scan.rb [options]" 22 | 23 | opts.on("-t", "--targets [TARGETS]", "A list of IP/CIDR Blocks") do |t| 24 | options.targets << t 25 | end 26 | 27 | opts.on("-p", "--ports [PORTS]", "A list of ports to scan") do |p| 28 | options.ports << p 29 | end 30 | 31 | opts.on("-i", "--interface [INTERFACE]", "The interface to scan from") do |i| 32 | options.interface = i 33 | end 34 | 35 | opts.on_tail("-h", "--help", "Show this message") do 36 | puts "" 37 | puts opts 38 | puts "" 39 | puts "Example: rvmsudo ruby bnat-scan.rb -t 192.168.1.1 -p 80" 40 | puts "" 41 | exit 42 | end 43 | end.parse! 44 | 45 | BNAT::Scanner.new( 46 | :iface => options.interface, 47 | :ports => options.ports.collect {|p| p.to_i}, 48 | :targets => options.targets 49 | ).scan 50 | -------------------------------------------------------------------------------- /bin/bnat-simulator: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') 4 | 5 | require 'bnat' 6 | require 'logger' 7 | 8 | unless Process.uid == 0 9 | puts 'You are not running as root, you need these rights for low-level packet control' 10 | exit 11 | end 12 | 13 | logger = Logger.new(STDOUT) 14 | logger.level = Logger::DEBUG 15 | 16 | port = 1337 17 | bnat_port = 1338 18 | 19 | config = PacketFu::Utils.whoami?() 20 | 21 | logger.debug("Starting Capture on #{config[:iface]}...") 22 | logger.debug("Scan me at #{config[:ip_saddr]}:#{port}") 23 | 24 | cap = PacketFu::Capture.new( 25 | :iface => config[:iface], 26 | :start => true, 27 | :filter => "tcp and dst #{config[:ip_saddr]} and port 1337 and tcp[13] == 2" 28 | ) 29 | 30 | begin 31 | 32 | # We're using the BNAT::Firewall to suppress 33 | # RSTs that will be generated by the OS because 34 | # the service isn't really listening, we're 35 | # just manually faking everything 36 | firewall = BNAT::Firewall.new() 37 | logger.debug("Detected #{firewall.type} firewall") 38 | logger.debug("Suppressing RSTs") 39 | firewall.suppress_rsts 40 | 41 | # Continue looping through packets that match 42 | # our filter and responding to them 43 | listen=Thread.new do 44 | loop do 45 | pkt = cap.next 46 | 47 | if pkt.nil? 48 | sleep 0.1 49 | next 50 | end 51 | 52 | synpkt = PacketFu::Packet.parse(pkt) 53 | logger.debug("Received SYN from #{synpkt.ip_saddr}") 54 | synackpkt = PacketFu::TCPPacket.new( 55 | :config=> config, 56 | :timeout=> 0.1, 57 | :flavor=> "Windows" 58 | ) 59 | 60 | # Feel free to hard code any of the 61 | # attributes in the reply you'd like 62 | # to forge. 63 | synackpkt.ip_saddr=synpkt.ip_daddr 64 | synackpkt.ip_daddr=synpkt.ip_saddr 65 | synackpkt.eth_saddr=synpkt.eth_daddr 66 | synackpkt.eth_daddr=synpkt.eth_saddr 67 | 68 | # SYN/ACKs will reply with BNAT port 69 | synackpkt.tcp_sport=bnat_port 70 | 71 | synackpkt.tcp_dport=synpkt.tcp_sport 72 | synackpkt.tcp_flags.syn=1 73 | synackpkt.tcp_flags.ack=1 74 | synackpkt.tcp_ack=synpkt.tcp_seq+1 75 | synackpkt.tcp_seq=rand(64511)+1024 76 | synackpkt.tcp_win=183 77 | synackpkt.recalc 78 | synackpkt.to_w 79 | 80 | logger.debug("Sent SYN/ACK to #{synpkt.ip_daddr}") 81 | end 82 | end 83 | 84 | listen.join 85 | 86 | ensure 87 | logger.debug("Removing RST Suppression Rules") 88 | firewall.remove_rules 89 | end 90 | -------------------------------------------------------------------------------- /bnat.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'bnat/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = "bnat" 8 | gem.version = Bnat::VERSION 9 | gem.authors = ["Jonathan Claudius"] 10 | gem.email = ["claudijd@yahoo.com"] 11 | gem.description = %q{A suite of tools focused on detecting/exploiting/fixing publicly available BNAT scenerios} 12 | gem.summary = %q{A suite of tools focused on detecting/exploiting/fixing publicly available BNAT scenerios} 13 | gem.homepage = "" 14 | 15 | gem.files = `git ls-files`.split($/) 16 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 17 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 18 | gem.require_paths = ["lib"] 19 | 20 | gem.add_dependency 'packetfu', '>= 1.1.10' 21 | gem.add_dependency 'pcaprub' 22 | gem.add_dependency 'network_interface' 23 | gem.add_dependency 'netaddr' 24 | gem.add_dependency 'json' 25 | gem.add_dependency 'nokogiri' 26 | 27 | gem.add_development_dependency 'bundler' 28 | gem.add_development_dependency 'rake' 29 | gem.add_development_dependency 'rspec' 30 | gem.add_development_dependency 'rspec-its' 31 | gem.add_development_dependency 'pry' 32 | end 33 | -------------------------------------------------------------------------------- /lib/bnat.rb: -------------------------------------------------------------------------------- 1 | # External Dependancies 2 | require 'packetfu' 3 | 4 | # Internal Depenandies 5 | require 'bnat/firewall' 6 | require 'bnat/version' 7 | require 'bnat/common' 8 | require 'bnat/tcp_packet' 9 | require 'bnat/scanner' 10 | require 'bnat/packet_factory' 11 | require 'bnat/capture_factory' 12 | require 'bnat/result' 13 | 14 | # Monkey Patches (this can go away once the next version of PacketFu is gemified) 15 | # This is basically a copy and paste job from PacketFu develop (no additional changes) 16 | require 'ext/packetfu/utils' -------------------------------------------------------------------------------- /lib/bnat/capture_factory.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'packetfu' 3 | require 'set' 4 | 5 | # A factory for generating capturing objects on demand 6 | 7 | module BNAT 8 | class CaptureFactory 9 | 10 | attr_reader :configs, :interface 11 | 12 | # @param [String] a string of the interface name 13 | def initialize(interface) 14 | @interface = interface 15 | end 16 | 17 | # Obtain a PacketFu::Capture Object based on a BPF and/or Interface 18 | # @param [String] the bpf/filter to use for the capture (optional) 19 | # @return [PacketFu::Capture] the capture object 20 | def get_capture(bpf = "tcp") 21 | ret = PacketFu::Capture.new( 22 | :iface => @interface, 23 | :start => true, 24 | :filter => bpf 25 | ) 26 | 27 | return ret 28 | end 29 | 30 | end 31 | end 32 | 33 | -------------------------------------------------------------------------------- /lib/bnat/common.rb: -------------------------------------------------------------------------------- 1 | require 'packetfu' 2 | require 'nokogiri' 3 | 4 | module BNAT 5 | module Common 6 | 7 | # Helper to turn a packet into a simplified hash 8 | # @param [PacketFu::TCPPacket] packet 9 | # @return [String] 10 | def packet_to_hash(packet) 11 | { 12 | :eth_saddr => packet.eth_saddr, 13 | :eth_daddr => packet.eth_daddr, 14 | :ip_saddr => packet.ip_saddr, 15 | :ip_daddr => packet.ip_daddr, 16 | :tcp_sport => packet.tcp_sport, 17 | :tcp_dport => packet.tcp_dport, 18 | :tcp_seq => packet.tcp_seq, 19 | :tcp_ack => packet.tcp_ack, 20 | :tcp_flags => packet.tcp_flags.to_h 21 | } 22 | end 23 | 24 | # Helper to turn a packet into a simplified hash 25 | # @param [PacketFu::TCPPacket] packet 26 | # @return [String] 27 | def packet_to_xml(packet) 28 | builder = Nokogiri::XML::Builder.new do |xml| 29 | xml.packet { 30 | xml.eth_saddr = packet.eth_saddr 31 | xml.eth_daddr = packet.eth_daddr 32 | xml.ip_saddr = packet.ip_saddr 33 | xml.ip_daddr = packet.ip_daddr 34 | xml.tcp_sport = packet.tcp_sport 35 | xml.tcp_dport = packet.tcp_dport 36 | xml.tcp_seq = packet.tcp_seq 37 | xml.tcp_ack = packet.tcp_ack 38 | xml.tcp_flags { 39 | xml.urg = packet.tcp_flags.to_h[:urg] 40 | xml.ack = packet.tcp_flags.to_h[:ack] 41 | xml.psh = packet.tcp_flags.to_h[:psh] 42 | xml.rst = packet.tcp_flags.to_h[:rst] 43 | xml.syn = packet.tcp_flags.to_h[:syn] 44 | xml.fin = packet.tcp_flags.to_h[:fin] 45 | } 46 | } 47 | end 48 | 49 | builder.doc.root.to_xml(:indent => 2) 50 | end 51 | 52 | # Helper for generating a TCPPacket 53 | # @param [PacketFu::Config] config (optional) 54 | # @return [PacketFu::TCPPacket] packet 55 | def get_tcp_packet(config = nil) 56 | PacketFu::TCPPacket.new( 57 | :config=> config, 58 | :timeout=> 0.1, 59 | :flavor=> "Windows" 60 | ) 61 | end 62 | 63 | # # A helper for generating TCP Packets 64 | # # @param [PacketFu::Config] config (optional) 65 | # # @return [PacketFu::TCPPacket] 66 | # def get_tcp_packet(config = nil) 67 | # BNAT::TCPPacket.new( 68 | # :config => config, 69 | # :timeout => 0.1, 70 | # :flavor => "Windows" 71 | # ) 72 | # end 73 | 74 | # # A helper for generating a reflective packets 75 | # # @param [PacketFu::TCPPacket] first_pkt 76 | # # @param [PacketFu::Config] config (optional) 77 | # # @return [PacketFu::TCPPacket] second_pkt 78 | # def get_reflective_packet(first_pkt, config = nil) 79 | # second_pkt = get_tcp_packet(config) 80 | 81 | # second_pkt.ip_saddr = first_pkt.ip_daddr 82 | # second_pkt.ip_daddr = first_pkt.ip_saddr 83 | # second_pkt.eth_saddr = first_pkt.eth_daddr 84 | # second_pkt.eth_daddr = first_pkt.eth_saddr 85 | # second_pkt.tcp_sport = first_pkt.tcp_dport 86 | # second_pkt.tcp_dport = first_pkt.tcp_sport 87 | 88 | # return second_pkt 89 | # end 90 | 91 | # # A helper for generating a reflective SYN/ACK 92 | # # @param [PacketFu::TCPPacket] syn_pkt 93 | # # @param [PacketFu::Config] config (optional) 94 | # # @return [PacketFu::TCPPacket] syn_ack_pkt 95 | # def get_reflective_syn_ack(syn_pkt, config = nil) 96 | # syn_ack_pkt = get_reflective_packet(syn_pkt, config) 97 | # syn_ack_pkt.tcp_flags.syn = 1 98 | # syn_ack_pkt.tcp_flags.ack = 1 99 | # syn_ack_pkt.tcp_ack = syn_pkt.tcp_seq + 1 100 | # syn_ack_pkt.tcp_seq = rand(64511) + 1024 101 | # syn_ack_pkt.tcp_win = 183 102 | 103 | # return syn_ack_pkt 104 | # end 105 | 106 | # # A helper for generating a reflective PSH/ACK 107 | # # @param [PacketFu::TCPPacket] ack_pkt 108 | # # @param [PacketFu::Config] config (optional) 109 | # # @return [PacketFu::TCPPacket] psh_ack_pkt 110 | # def get_reflective_psh_ack(ack_pkt, config = nil) 111 | # psh_ack_pkt = get_reflective_packet(ack_pkt, config) 112 | # psh_ack_pkt.tcp_flags.syn = 0 113 | # psh_ack_pkt.tcp_flags.psh = 1 114 | # psh_ack_pkt.tcp_flags.ack = 1 115 | # psh_ack_pkt.tcp_ack = ack_pkt.tcp_seq 116 | # psh_ack_pkt.tcp_seq = ack_pkt.tcp_ack 117 | 118 | # return psh_ack_pkt 119 | # end 120 | 121 | # # A helper for generating a reflective PSH/ACK 122 | # # @param [PacketFu::TCPPacket] syn_ack_pkt 123 | # # @param [PacketFu::Config] config (optional) 124 | # # @return [PacketFu::TCPPacket] ack_pkt 125 | # def get_reflective_ack(syn_ack_pkt, config = nil) 126 | # ack_pkt = get_reflective_packet(syn_ack_pkt, config) 127 | # ack_pkt.tcp_flags.syn = 0 128 | # ack_pkt.tcp_flags.psh = 0 129 | # ack_pkt.tcp_flags.ack = 1 130 | # ack_pkt.tcp_ack = syn_ack_pkt.tcp_seq + 1 131 | # ack_pkt.tcp_seq = syn_ack_pkt.tcp_ack 132 | 133 | # return ack_pkt 134 | # end 135 | 136 | # # A helper for generating packet captures 137 | # # @param [String] int - The interface name 138 | # # @param [String] filter - The BPF filter for the capture 139 | # # @return [PacketFu::Capture] 140 | # def get_capture(int, filter) 141 | # PacketFu::Capture.new( 142 | # :iface => int, 143 | # :start => true, 144 | # :filter => filter 145 | # ) 146 | # end 147 | 148 | # # A helper for generating a reflective BPF filter string 149 | # # @param [PacketFu::TCPPacket] pkt 150 | # # @param [String] filter - The BPF filter that's reflective to this packet 151 | # def get_reflective_bpf(pkt) 152 | # "src #{pkt.ip_daddr} " + 153 | # "and dst #{pkt.ip_saddr} " + 154 | # "and src port #{pkt.tcp_dport} " + 155 | # "and dst port #{pkt.tcp_sport}" 156 | # end 157 | 158 | end 159 | end 160 | 161 | -------------------------------------------------------------------------------- /lib/bnat/firewall.rb: -------------------------------------------------------------------------------- 1 | module BNAT 2 | 3 | class IPFW 4 | def initialize(path) 5 | @path = path 6 | @rules_added = [] 7 | end 8 | 9 | def type 10 | "IPFW" 11 | end 12 | 13 | def rules 14 | `ipfw list`.chomp.split("\n") 15 | end 16 | 17 | def suppress_rsts(ip, opts = {}) 18 | ret = `ipfw add deny tcp from #{ip} to any tcpflags rst`.chomp 19 | 20 | if match = ret.match(/^(\d+)/) 21 | @rules_added << match[1] 22 | else 23 | raise "Failed to add rule: #{ret}" 24 | end 25 | end 26 | 27 | def remove_rules 28 | @rules_added.each do |rule| 29 | `ipfw delete #{rule}` 30 | end 31 | 32 | @rules_added = [] 33 | 34 | return rules 35 | end 36 | end 37 | 38 | class IPTables 39 | def initialize(path) 40 | @path = path 41 | @rules_added = [] 42 | end 43 | 44 | def type 45 | "IPTables" 46 | end 47 | 48 | def rules 49 | # TODO: implement this 50 | raise "Not implemented yet, sorry" 51 | end 52 | 53 | def suppress_rsts(ip, opts = {}) 54 | `iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP`.chomp 55 | end 56 | 57 | def remove_rules 58 | # TODO: implement this 59 | raise "Not implemented yet, sorry" 60 | end 61 | end 62 | 63 | class Firewall 64 | def initialize(opts = {}) 65 | @firewall = opts[:firewall] || detect_firewall 66 | end 67 | 68 | def detect_firewall 69 | ipfw_path = `which ipfw`.chomp 70 | iptables_path = `which iptables`.chomp 71 | 72 | if ipfw_path.size > 0 73 | return BNAT::IPFW.new(ipfw_path) 74 | elsif iptables_path.size > 0 75 | return BNAT::IPTables.new(iptables_path) 76 | else 77 | raise "Unable to detect firewall (ipfw/iptables)" 78 | end 79 | end 80 | 81 | def type 82 | @firewall.type 83 | end 84 | 85 | def suppress_rsts(ip = PacketFu::Utils.default_ip) 86 | @firewall.suppress_rsts(ip) 87 | end 88 | 89 | def rules 90 | @firewall.rules 91 | end 92 | 93 | def remove_rules 94 | @firewall.remove_rules 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /lib/bnat/packet_factory.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'packetfu' 3 | 4 | # A factory for generating packet objects on demand 5 | 6 | module BNAT 7 | class PacketFactory 8 | 9 | # @param [String] a string of the interface name 10 | def initialize(interface) 11 | @interface = interface 12 | end 13 | 14 | #Get a Generic TCP Packet 15 | def get_tcp_packet 16 | tcp_packet = PacketFu::TCPPacket.new( 17 | :config=> PacketFu::Utils.whoami?(:iface => "#{@interface}"), 18 | :timeout=> 0.1, 19 | :flavor=>"Windows" 20 | ) 21 | 22 | return tcp_packet 23 | end 24 | 25 | # Get a TCP SYN Probe Packet 26 | # @param opts [Integer] :port the destination port to target 27 | # @param opts [String] :ip_daddr the destination port to target 28 | def get_syn_probe(opts = {}) 29 | tcp_syn_probe = get_tcp_packet() 30 | tcp_syn_probe.tcp_flags.syn=1 31 | tcp_syn_probe.tcp_win=14600 32 | tcp_syn_probe.tcp_options="MSS:1460,SACKOK,TS:3853;0,NOP,WS:5" 33 | tcp_syn_probe.tcp_src = rand(64511)+1024 34 | tcp_syn_probe.tcp_seq = rand(2**32-10**9)+10**9 35 | tcp_syn_probe.tcp_dst = opts[:port].to_i if opts[:port] 36 | tcp_syn_probe.ip_daddr = opts[:ip] if opts[:ip] 37 | 38 | return tcp_syn_probe 39 | end 40 | 41 | # Get a TCP ACK Probe Packet 42 | def get_ack_probe() 43 | tcp_ack_probe = get_tcp_packet() 44 | tcp_ack_probe.tcp_flags.syn = 0 45 | tcp_ack_probe.tcp_flags.ack = 1 46 | 47 | return tcp_ack_probe 48 | end 49 | 50 | # Get a reflective TCP ACK Probe Packet from a TCP SYN/ACK Packet 51 | # @param [PacketFu:TCPPacket] a syn/ack packet 52 | def get_ack_from_syn_ack(syn_ack) 53 | ack = get_ack_probe() 54 | ack.ip_saddr = syn_ack.ip_daddr 55 | ack.ip_daddr = syn_ack.ip_saddr 56 | ack.eth_saddr = syn_ack.eth_daddr 57 | ack.eth_daddr = syn_ack.eth_saddr 58 | ack.tcp_sport = syn_ack.tcp_dport 59 | ack.tcp_dport = syn_ack.tcp_sport 60 | ack.tcp_ack = syn_ack.tcp_seq+1 61 | ack.tcp_seq = syn_ack.tcp_ack 62 | ack.tcp_win = 183 63 | 64 | return ack 65 | end 66 | 67 | end 68 | end 69 | 70 | -------------------------------------------------------------------------------- /lib/bnat/result.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | module BNAT 4 | class Result 5 | include BNAT::Common 6 | 7 | def initialize(request_packet, response_packet) 8 | @request_packet = request_packet 9 | @response_packet = response_packet 10 | end 11 | 12 | def to_hash 13 | { 14 | :request_packet => packet_to_hash(@request_packet), 15 | :response_packet => packet_to_hash(@response_packet) 16 | } 17 | end 18 | 19 | def to_json 20 | JSON.generate(self.to_hash) 21 | end 22 | 23 | def to_xml 24 | builder = Nokogiri::XML::Builder.new do |xml| 25 | xml.result { 26 | xml.request_packet { 27 | xml << packet_to_xml(@request_packet) 28 | } 29 | xml.response_packet { 30 | xml << packet_to_xml(@response_packet) 31 | } 32 | } 33 | end 34 | 35 | builder.doc.root.to_xml 36 | end 37 | 38 | def to_s 39 | "BNAT Service at #{@request_packet.ip_daddr}:#{@request_packet.tcp_dport}\n" + 40 | "\tRequest: #{@request_packet.ip_daddr}:#{@request_packet.tcp_dport} (Seq: #{@request_packet.tcp_seq})\n" + 41 | "\tResponse: #{@response_packet.ip_saddr}:#{@response_packet.tcp_sport} (Ack: #{@response_packet.tcp_ack})" 42 | end 43 | 44 | end 45 | end -------------------------------------------------------------------------------- /lib/bnat/scanner.rb: -------------------------------------------------------------------------------- 1 | require 'packetfu' 2 | require 'netaddr' 3 | require 'bnat' 4 | require 'pp' 5 | 6 | module BNAT 7 | class Scanner 8 | 9 | attr_accessor :iface, :ports, :targets 10 | 11 | # @param opts [String] :iface - a string of the interface name 12 | # @param opts [Array] :ports - an array of ports to scan 13 | # @param opts [Array] :targets - an array of ports to scan 14 | # @param opts [String] :mode - the mode run the scan as ("normal"/"fast") 15 | def initialize(opts = {}) 16 | @iface = opts[:iface] 17 | @ports = opts[:ports] 18 | @targets = opts[:targets] 19 | @response_timeout = opts[:response_timeout] || 0.05 20 | @pf = BNAT::PacketFactory.new(@iface) 21 | @cf = BNAT::CaptureFactory.new(@iface) 22 | end 23 | 24 | # @param [String] a string IP to scan 25 | def scan_ip(ip) 26 | ret = [] 27 | 28 | @ports.each do |port| 29 | 30 | #Generate a syn probe 31 | syn_pkt = @pf.get_syn_probe(:ip => ip, :port => port) 32 | 33 | #Define a BPF filter for responses 34 | 35 | # live (check for IP-based bnat) 36 | #bpf = "tcp and not host #{ip} and tcp[13] == 18 and " + 37 | # "tcp [8:4] == 0x#{(syn_pkt.tcp_seq + 1).to_s(16)}" 38 | 39 | # live (check for port-based bnat) 40 | bpf = "tcp and not port #{port} and host #{ip} and tcp[13] == 18 and " + 41 | "tcp [8:4] == 0x#{(syn_pkt.tcp_seq + 1).to_s(16)}" 42 | 43 | #Create a Capture to look for responses 44 | pcap = @cf.get_capture(bpf) 45 | 46 | #Send the Packet to the Wire 47 | scan = Thread.new do 48 | syn_pkt.recalc 49 | syn_pkt.to_w 50 | sleep 0.075 51 | syn_pkt.to_w 52 | end 53 | 54 | analyze = Thread.new do 55 | loop do 56 | pcap.stream.each do |pkt| 57 | syn_ack_pkt = PacketFu::Packet.parse(pkt) 58 | ret << BNAT::Result.new(syn_pkt, syn_ack_pkt) 59 | 60 | self.terminate 61 | end 62 | end 63 | end 64 | 65 | scan.join 66 | 67 | sleep @response_timeout 68 | analyze.terminate 69 | end 70 | 71 | return ret 72 | end 73 | 74 | def scan 75 | @targets.each do |target| 76 | 77 | cidr = NetAddr::CIDR.create(target) 78 | 79 | if cidr.size == 0 80 | puts "No addresses within provided target '#{target}'" 81 | else 82 | start = NetAddr::CIDR.create(cidr.first) 83 | fin = NetAddr::CIDR.create(cidr.last) 84 | 85 | (start..fin).each do |addr| 86 | scan_ip(addr.ip).each do |bnat_result| 87 | puts bnat_result.to_s 88 | end 89 | end 90 | end 91 | 92 | end 93 | end 94 | 95 | end 96 | end -------------------------------------------------------------------------------- /lib/bnat/tcp_packet.rb: -------------------------------------------------------------------------------- 1 | require 'packetfu' 2 | 3 | module BNAT 4 | class TCPPacket < PacketFu::TCPPacket 5 | 6 | # Check to see if values are equal 7 | # @param [Bnat::Packet] an arbitrary Bnat::Packet object 8 | # @return [true, false] response is either true or false 9 | def ==(other) 10 | self.ip_saddr == other.ip_daddr 11 | self.ip_daddr == other.ip_saddr 12 | self.tcp_sport == other.tcp_dport 13 | self.tcp_dport == other.tcp_sport 14 | self.tcp_seq == other.tcp_seq && 15 | self.tcp_ack == other.tcp_ack 16 | end 17 | 18 | # Check to see if values are equal and of the same type 19 | # @param [Bnat::Packet] an arbitrary Bnat::Packet object 20 | # @return [true, false] response is either true or false 21 | def eql?(other) 22 | self.class == other.class && 23 | self == other 24 | end 25 | 26 | # Check to see if packets are an IP-based bnat pair 27 | # @param [Bnat::Packet] an arbitrary Bnat::Packet object 28 | # @return [true, false] response is either true or false 29 | def ip_bnat?(other) 30 | src_ip_match?(other) == false && 31 | dst_ip_match?(other) == true && 32 | port_match?(other) == true && 33 | ack_match?(other) == true 34 | end 35 | 36 | # Check to see if packets are an Port-based bnat pair 37 | # @param [Bnat::Packet] an arbitrary Bnat::Packet object 38 | # @return [true, false] response is either true or false 39 | def port_bnat?(other) 40 | ip_match?(other) == true && 41 | src_port_match?(other) == false && 42 | dst_port_match?(other) == true && 43 | ack_match?(other) == true 44 | end 45 | 46 | # Check to see if packets are an IP & Port-based bnat pair 47 | # @param [Bnat::Packet] an arbitrary Bnat::Packet object 48 | # @return [true, false] response is either true or false 49 | def ip_port_bnat?(other) 50 | src_ip_match?(other) == false && 51 | dst_ip_match?(other) == true && 52 | src_port_match?(other) == false && 53 | dst_port_match?(other) == true && 54 | ack_match?(other) == true 55 | end 56 | 57 | # Determine if the ips of the packets 58 | # reflectively match 59 | # @param [Bnat::Packet] other 60 | # @return [true, false] 61 | def ip_match?(other) 62 | src_ip_match?(other) && 63 | dst_ip_match?(other) 64 | end 65 | 66 | # Determine if the ports of the packets 67 | # reflectively match 68 | # @param [Bnat::Packet] other 69 | # @return [true, false] 70 | def port_match?(other) 71 | src_port_match?(other) && 72 | dst_port_match?(other) 73 | end 74 | 75 | # Determine if the src ip of the packet 76 | # matches the dst ip we sent to 77 | # @param [Bnat::Packet] other 78 | # @return [true, false] 79 | def src_ip_match?(other) 80 | self.ip_daddr == other.ip_saddr 81 | end 82 | 83 | # Determine if the dst ip of the packet 84 | # matches the src ip we sent from 85 | # @param [Bnat::Packet] other 86 | # @return [true, false] 87 | def dst_ip_match?(other) 88 | self.ip_saddr == other.ip_daddr 89 | end 90 | 91 | # Determine if the src port of the packet 92 | # matches the dst port we sent to 93 | # @param [Bnat::Packet] other 94 | # @return [true, false] 95 | def src_port_match?(other) 96 | self.tcp_dport == other.tcp_sport 97 | end 98 | 99 | # Determine if the dst port of the packet 100 | # matches the src port we sent from 101 | # @param [Bnat::Packet] other 102 | # @return [true, false] 103 | def dst_port_match?(other) 104 | self.tcp_sport == other.tcp_dport 105 | end 106 | 107 | # Determine if the ack of the packet 108 | # matches the seq we sent from 109 | # @param [Bnat::Packet] other 110 | # @return [true, false] 111 | def ack_match?(other) 112 | self.tcp_seq + 1 == other.tcp_ack 113 | end 114 | 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /lib/bnat/version.rb: -------------------------------------------------------------------------------- 1 | module Bnat 2 | VERSION = "0.0.1" 3 | end 4 | -------------------------------------------------------------------------------- /lib/ext/packetfu/utils.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: binary -*- 2 | require 'singleton' 3 | require 'timeout' 4 | require 'network_interface' 5 | 6 | module PacketFu 7 | 8 | # Utils is a collection of various and sundry network utilities that are useful for packet 9 | # manipulation. 10 | class Utils 11 | 12 | # Returns the MAC address of an IP address, or nil if it's not responsive to arp. Takes 13 | # a dotted-octect notation of the target IP address, as well as a number of parameters: 14 | # 15 | # === Parameters 16 | # :iface 17 | # Interface. Defaults to "eth0" 18 | # :eth_saddr 19 | # Source MAC address. Defaults to "00:00:00:00:00:00". 20 | # :ip_saddr 21 | # Source IP address. Defaults to "0.0.0.0" 22 | # :flavor 23 | # The flavor of the ARP request. Defaults to :none. 24 | # :timeout 25 | # Timeout in seconds. Defaults to 3. 26 | # 27 | # === Example 28 | # PacketFu::Utils::arp("192.168.1.1") #=> "00:18:39:01:33:70" 29 | # PacketFu::Utils::arp("192.168.1.1", :iface => "wlan2", :timeout => 5, :flavor => :hp_deskjet) 30 | # 31 | # === Warning 32 | # 33 | # It goes without saying, spewing forged ARP packets on your network is a great way to really 34 | # irritate your co-workers. 35 | def self.arp(target_ip,args={}) 36 | iface = args[:iface] || :eth0 37 | args[:config] ||= whoami?(:iface => iface) 38 | arp_pkt = PacketFu::ARPPacket.new(:flavor => (args[:flavor] || :none), :config => args[:config]) 39 | arp_pkt.eth_daddr = "ff:ff:ff:ff:ff:ff" 40 | arp_pkt.arp_daddr_mac = "00:00:00:00:00:00" 41 | arp_pkt.arp_daddr_ip = target_ip 42 | # Stick the Capture object in its own thread. 43 | cap_thread = Thread.new do 44 | target_mac = nil 45 | cap = PacketFu::Capture.new(:iface => iface, :start => true, 46 | :filter => "arp src #{target_ip} and ether dst #{arp_pkt.eth_saddr}") 47 | arp_pkt.to_w(iface) # Shorthand for sending single packets to the default interface. 48 | timeout = 0 49 | while target_mac.nil? && timeout <= (args[:timeout] || 3) 50 | if cap.save > 0 51 | arp_response = PacketFu::Packet.parse(cap.array[0]) 52 | target_mac = arp_response.arp_saddr_mac if arp_response.arp_saddr_ip = target_ip 53 | end 54 | timeout += 0.1 55 | sleep 0.1 # Check for a response ten times per second. 56 | end 57 | target_mac 58 | end # cap_thread 59 | cap_thread.value 60 | end 61 | 62 | # Since 177/8 is IANA reserved (for now), this network should 63 | # be handled by your default gateway and default interface. 64 | def self.rand_routable_daddr 65 | IPAddr.new((rand(16777216) + 2969567232), Socket::AF_INET) 66 | end 67 | 68 | # A helper for getting a random port number 69 | def self.rand_port 70 | rand(0xffff-1024)+1024 71 | end 72 | 73 | # Discovers the local IP and Ethernet address, which is useful for writing 74 | # packets you expect to get a response to. Note, this is a noisy 75 | # operation; a UDP packet is generated and dropped on to the default (or named) 76 | # interface, and then captured (which means you need to be root to do this). 77 | # 78 | # whoami? returns a hash of :eth_saddr, :eth_src, :ip_saddr, :ip_src, 79 | # :ip_src_bin, :eth_dst, and :eth_daddr (the last two are usually suitable 80 | # for a gateway mac address). It's most useful as an argument to 81 | # PacketFu::Config.new, or as an argument to the many Packet constructors. 82 | # 83 | # Note that if you have multiple interfaces with the same route (such as when 84 | # wlan0 and eth0 are associated to the same network), the "first" one 85 | # according to Pcap.lookupdev will be used, regardless of which :iface you 86 | # pick. 87 | # 88 | # === Parameters 89 | # :iface => "eth0" 90 | # An interface to listen for packets on. Note that since we rely on the OS to send the probe packet, 91 | # you will need to specify a target which will use this interface. 92 | # :target => "1.2.3.4" 93 | # A target IP address. By default, a packet will be sent to a random address in the 177/8 network. 94 | def self.whoami?(args={}) 95 | unless args.kind_of? Hash 96 | raise ArgumentError, "Argument to `whoami?' must be a Hash" 97 | end 98 | if args[:iface].to_s =~ /^lo/ # Linux loopback more or less. Need a switch for windows loopback, too. 99 | dst_host = "127.0.0.1" 100 | else 101 | dst_host = (args[:target] || rand_routable_daddr.to_s) 102 | end 103 | 104 | dst_port = rand_port 105 | msg = "PacketFu whoami? packet #{(Time.now.to_i + rand(0xffffff)+1)}" 106 | iface = (args[:iface] || ENV['IFACE'] || default_int || :lo ).to_s 107 | cap = PacketFu::Capture.new(:iface => iface, :promisc => false, :start => true, :filter => "udp and dst host #{dst_host} and dst port #{dst_port}") 108 | udp_sock = UDPSocket.new 109 | udp_sock.send(msg,0,dst_host,dst_port) 110 | udp_sock = nil 111 | 112 | my_data = nil 113 | 114 | begin 115 | Timeout::timeout(1) { 116 | pkt = nil 117 | 118 | while pkt.nil? 119 | raw_pkt = cap.next 120 | next if raw_pkt.nil? 121 | 122 | pkt = Packet.parse(raw_pkt) 123 | 124 | if pkt.payload == msg 125 | 126 | my_data = { 127 | :iface => (args[:iface] || ENV['IFACE'] || default_int || "lo").to_s, 128 | :pcapfile => args[:pcapfile] || "/tmp/out.pcap", 129 | :eth_saddr => pkt.eth_saddr, 130 | :eth_src => pkt.eth_src.to_s, 131 | :ip_saddr => pkt.ip_saddr, 132 | :ip_src => pkt.ip_src, 133 | :ip_src_bin => [pkt.ip_src].pack("N"), 134 | :eth_dst => pkt.eth_dst.to_s, 135 | :eth_daddr => pkt.eth_daddr 136 | } 137 | 138 | else raise SecurityError, 139 | "whoami() packet doesn't match sent data. Something fishy's going on." 140 | end 141 | 142 | end 143 | } 144 | rescue Timeout::Error 145 | raise SocketError, "Didn't receive the whoami() packet, can't automatically configure." 146 | end 147 | 148 | my_data 149 | end 150 | 151 | # Determine the default ip address 152 | def self.default_ip 153 | begin 154 | orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily 155 | 156 | UDPSocket.open do |s| 157 | s.connect rand_routable_daddr.to_s, rand_port 158 | s.addr.last 159 | end 160 | ensure 161 | Socket.do_not_reverse_lookup = orig 162 | end 163 | end 164 | 165 | # Determine the default routeable interface 166 | def self.default_int 167 | ip = default_ip 168 | 169 | NetworkInterface.interfaces.each do |interface| 170 | NetworkInterface.addresses(interface).values.each do |addresses| 171 | addresses.each do |address| 172 | next if address["addr"].nil? 173 | return interface if address["addr"] == ip 174 | end 175 | end 176 | end 177 | 178 | # Fall back to libpcap as last resort 179 | return Pcap.lookupdev 180 | end 181 | 182 | # Handles ifconfig for various (okay, two) platforms. 183 | # Will have Windows done shortly. 184 | # 185 | # Takes an argument (either string or symbol) of the interface to look up, and 186 | # returns a hash which contains at least the :iface element, and if configured, 187 | # these additional elements: 188 | # 189 | # :eth_saddr # A human readable MAC address 190 | # :eth_src # A packed MAC address 191 | # :ip_saddr # A dotted-quad string IPv4 address 192 | # :ip_src # A packed IPv4 address 193 | # :ip4_obj # An IPAddr object with bitmask 194 | # :ip6_saddr # A colon-delimited hex IPv6 address, with bitmask 195 | # :ip6_obj # An IPAddr object with bitmask 196 | # 197 | # === Example 198 | # PacketFu::Utils.ifconfig :wlan0 # Not associated yet 199 | # #=> {:eth_saddr=>"00:1d:e0:73:9d:ff", :eth_src=>"\000\035\340s\235\377", :iface=>"wlan0"} 200 | # PacketFu::Utils.ifconfig("eth0") # Takes 'eth0' as default 201 | # #=> {:eth_saddr=>"00:1c:23:35:70:3b", :eth_src=>"\000\034#5p;", :ip_saddr=>"10.10.10.9", :ip4_obj=>#, :ip_src=>"\n\n\n\t", :iface=>"eth0", :ip6_saddr=>"fe80::21c:23ff:fe35:703b/64", :ip6_obj=>#} 202 | # PacketFu::Utils.ifconfig :lo 203 | # #=> {:ip_saddr=>"127.0.0.1", :ip4_obj=>#, :ip_src=>"\177\000\000\001", :iface=>"lo", :ip6_saddr=>"::1/128", :ip6_obj=>#} 204 | def self.ifconfig(iface='eth0') 205 | ret = {} 206 | iface = iface.to_s.scan(/[0-9A-Za-z]/).join # Sanitizing input, no spaces, semicolons, etc. 207 | case RUBY_PLATFORM 208 | when /linux/i 209 | ifconfig_data = %x[ifconfig #{iface}] 210 | if ifconfig_data =~ /#{iface}/i 211 | ifconfig_data = ifconfig_data.split(/[\s]*\n[\s]*/) 212 | else 213 | raise ArgumentError, "Cannot ifconfig #{iface}" 214 | end 215 | real_iface = ifconfig_data.first 216 | ret[:iface] = real_iface.split.first.downcase 217 | if real_iface =~ /[\s]HWaddr[\s]+([0-9a-fA-F:]{17})/i 218 | ret[:eth_saddr] = $1.downcase 219 | ret[:eth_src] = EthHeader.mac2str(ret[:eth_saddr]) 220 | end 221 | ifconfig_data.each do |s| 222 | case s 223 | when /inet addr:[\s]*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(.*Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+))?/i 224 | ret[:ip_saddr] = $1 225 | ret[:ip_src] = [IPAddr.new($1).to_i].pack("N") 226 | ret[:ip4_obj] = IPAddr.new($1) 227 | ret[:ip4_obj] = ret[:ip4_obj].mask($3) if $3 228 | when /inet6 addr:[\s]*([0-9a-fA-F:\x2f]+)/ 229 | ret[:ip6_saddr] = $1 230 | ret[:ip6_obj] = IPAddr.new($1) 231 | end 232 | end # linux 233 | when /darwin/i 234 | ifconfig_data = %x[ifconfig #{iface}] 235 | if ifconfig_data =~ /#{iface}/i 236 | ifconfig_data = ifconfig_data.split(/[\s]*\n[\s]*/) 237 | else 238 | raise ArgumentError, "Cannot ifconfig #{iface}" 239 | end 240 | real_iface = ifconfig_data.first 241 | ret[:iface] = real_iface.split(':')[0] 242 | ifconfig_data.each do |s| 243 | case s 244 | when /ether[\s]([0-9a-fA-F:]{17})/i 245 | ret[:eth_saddr] = $1 246 | ret[:eth_src] = EthHeader.mac2str(ret[:eth_saddr]) 247 | when /inet[\s]*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(.*Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+))?/i 248 | ret[:ip_saddr] = $1 249 | ret[:ip_src] = [IPAddr.new($1).to_i].pack("N") 250 | ret[:ip4_obj] = IPAddr.new($1) 251 | ret[:ip4_obj] = ret[:ip4_obj].mask($3) if $3 252 | when /inet6[\s]*([0-9a-fA-F:\x2f]+)/ 253 | ret[:ip6_saddr] = $1 254 | ret[:ip6_obj] = IPAddr.new($1) 255 | end 256 | end # darwin 257 | end # RUBY_PLATFORM 258 | ret 259 | end 260 | 261 | end 262 | 263 | end 264 | 265 | # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby -------------------------------------------------------------------------------- /spec/bnat/capture_factory_spec.rb: -------------------------------------------------------------------------------- 1 | #require 'spec_helper' 2 | #require 'rubygems' 3 | #require 'bnat' 4 | #require 'packetfu' 5 | 6 | #module Bnat 7 | # describe CaptureFactory do 8 | # 9 | # before :each do 10 | # @cf = Bnat::CaptureFactory.new('eth0') 11 | # end 12 | 13 | # context "when initializing a bnat capture factory" do 14 | # subject{@cf} 15 | # its(:class) {should == Bnat::CaptureFactory} 16 | # end 17 | 18 | # context "when getting a capture" do 19 | # subject{@cf} 20 | 21 | # it "should be the right capture class" do 22 | # @cf.get_capture.class.should == PacketFu::Capture 23 | # end 24 | # end 25 | 26 | # end 27 | #end 28 | -------------------------------------------------------------------------------- /spec/bnat/common_spec.rb: -------------------------------------------------------------------------------- 1 | require 'bnat' 2 | require 'rspec/its' 3 | 4 | module BNAT 5 | describe Common do 6 | before :each do 7 | @helper = class Helper 8 | include BNAT::Common 9 | end.new 10 | end 11 | 12 | context "when generating a packet with #get_tcp_packet" do 13 | it "should generate a TCP packet" do 14 | @helper.get_tcp_packet.should be_kind_of(PacketFu::TCPPacket) 15 | end 16 | end 17 | 18 | context "when converting a packet to a hash" do 19 | it "should convert a TCP packet into a hash" do 20 | packet = @helper.get_tcp_packet 21 | @helper.packet_to_hash(packet).should be_kind_of(::Hash) 22 | end 23 | end 24 | 25 | # it "should generate a TCP packet" do 26 | # @helper.get_tcp_packet().should be_kind_of(BNAT::TCPPacket) 27 | # end 28 | 29 | # context "when generating generic reflective packets" do 30 | # before :each do 31 | # @first_pkt = @helper.get_tcp_packet() 32 | # @first_pkt.ip_saddr = "192.168.1.1" 33 | # @first_pkt.ip_daddr = "192.168.1.2" 34 | # @first_pkt.eth_saddr = "aa:aa:aa:aa:aa:aa" 35 | # @first_pkt.eth_daddr = "bb:bb:bb:bb:bb:bb" 36 | # @first_pkt.tcp_sport = 12345 37 | # @first_pkt.tcp_dport = 80 38 | 39 | # @second = @helper.get_reflective_packet(@first_pkt) 40 | # end 41 | 42 | # subject{@second} 43 | 44 | # its(:class) {should == BNAT::TCPPacket} 45 | # its(:ip_saddr) {should == "192.168.1.2"} 46 | # its(:ip_daddr) {should == "192.168.1.1"} 47 | # its(:eth_saddr) {should == "bb:bb:bb:bb:bb:bb"} 48 | # its(:eth_daddr) {should == "aa:aa:aa:aa:aa:aa"} 49 | # its(:tcp_sport) {should == 80} 50 | # its(:tcp_dport) {should == 12345} 51 | # end 52 | 53 | # context "when generating reflective syn ack packets" do 54 | # before :each do 55 | # @syn_pkt = @helper.get_tcp_packet() 56 | # @syn_pkt.ip_saddr = "192.168.1.1" 57 | # @syn_pkt.ip_daddr = "192.168.1.2" 58 | # @syn_pkt.eth_saddr = "aa:aa:aa:aa:aa:aa" 59 | # @syn_pkt.eth_daddr = "bb:bb:bb:bb:bb:bb" 60 | # @syn_pkt.tcp_sport = 12345 61 | # @syn_pkt.tcp_dport = 80 62 | # @syn_pkt.tcp_flags.syn = 1 63 | # @syn_pkt.tcp_flags.ack = 0 64 | 65 | # @syn_ack_pkt = @helper.get_reflective_syn_ack(@syn_pkt) 66 | # end 67 | 68 | # subject{@syn_ack_pkt} 69 | 70 | # its(:class) {should == BNAT::TCPPacket} 71 | # its(:ip_saddr) {should == "192.168.1.2"} 72 | # its(:ip_daddr) {should == "192.168.1.1"} 73 | # its(:eth_saddr) {should == "bb:bb:bb:bb:bb:bb"} 74 | # its(:eth_daddr) {should == "aa:aa:aa:aa:aa:aa"} 75 | # its(:tcp_sport) {should == 80} 76 | # its(:tcp_dport) {should == 12345} 77 | # its(:tcp_win) {should == 183} 78 | # its(:tcp_ack) {should == @syn_pkt.tcp_seq + 1} 79 | 80 | # it "should have the right tcp_flags" do 81 | # subject.tcp_flags.syn.should == 1 82 | # subject.tcp_flags.ack.should == 1 83 | # end 84 | # end 85 | 86 | # context "when generating reflective psh ack packets" do 87 | # before :each do 88 | # @ack_pkt = @helper.get_tcp_packet() 89 | # @ack_pkt.ip_saddr = "192.168.1.1" 90 | # @ack_pkt.ip_daddr = "192.168.1.2" 91 | # @ack_pkt.eth_saddr = "aa:aa:aa:aa:aa:aa" 92 | # @ack_pkt.eth_daddr = "bb:bb:bb:bb:bb:bb" 93 | # @ack_pkt.tcp_sport = 12345 94 | # @ack_pkt.tcp_dport = 80 95 | # @ack_pkt.tcp_flags.syn = 1 96 | # @ack_pkt.tcp_flags.ack = 1 97 | # @ack_pkt.tcp_seq = 11111 98 | # @ack_pkt.tcp_ack = 22222 99 | 100 | # @psh_ack_pkt = @helper.get_reflective_psh_ack(@ack_pkt) 101 | # end 102 | 103 | # subject{@psh_ack_pkt} 104 | 105 | # its(:class) {should == BNAT::TCPPacket} 106 | # its(:ip_saddr) {should == "192.168.1.2"} 107 | # its(:ip_daddr) {should == "192.168.1.1"} 108 | # its(:eth_saddr) {should == "bb:bb:bb:bb:bb:bb"} 109 | # its(:eth_daddr) {should == "aa:aa:aa:aa:aa:aa"} 110 | # its(:tcp_sport) {should == 80} 111 | # its(:tcp_dport) {should == 12345} 112 | # its(:tcp_seq) {should == @ack_pkt.tcp_ack} 113 | # its(:tcp_ack) {should == @ack_pkt.tcp_seq} 114 | 115 | # it "should have the right tcp_flags" do 116 | # subject.tcp_flags.syn.should == 0 117 | # subject.tcp_flags.psh.should == 1 118 | # subject.tcp_flags.ack.should == 1 119 | # end 120 | # end 121 | 122 | # context "when generating reflective ack packets" do 123 | # before :each do 124 | # @syn_ack_pkt = @helper.get_tcp_packet() 125 | # @syn_ack_pkt.ip_saddr = "192.168.1.1" 126 | # @syn_ack_pkt.ip_daddr = "192.168.1.2" 127 | # @syn_ack_pkt.eth_saddr = "aa:aa:aa:aa:aa:aa" 128 | # @syn_ack_pkt.eth_daddr = "bb:bb:bb:bb:bb:bb" 129 | # @syn_ack_pkt.tcp_sport = 12345 130 | # @syn_ack_pkt.tcp_dport = 80 131 | # @syn_ack_pkt.tcp_flags.syn = 1 132 | # @syn_ack_pkt.tcp_flags.ack = 1 133 | # @syn_ack_pkt.tcp_flags.psh = 0 134 | # @syn_ack_pkt.tcp_seq = 11111 135 | # @syn_ack_pkt.tcp_ack = 22222 136 | 137 | # @ack_pkt = @helper.get_reflective_ack(@syn_ack_pkt) 138 | # end 139 | 140 | # subject{@ack_pkt} 141 | 142 | # its(:class) {should == BNAT::TCPPacket} 143 | # its(:ip_saddr) {should == "192.168.1.2"} 144 | # its(:ip_daddr) {should == "192.168.1.1"} 145 | # its(:eth_saddr) {should == "bb:bb:bb:bb:bb:bb"} 146 | # its(:eth_daddr) {should == "aa:aa:aa:aa:aa:aa"} 147 | # its(:tcp_sport) {should == 80} 148 | # its(:tcp_dport) {should == 12345} 149 | # its(:tcp_seq) {should == @syn_ack_pkt.tcp_ack} 150 | # its(:tcp_ack) {should == @syn_ack_pkt.tcp_seq + 1} 151 | 152 | # it "should have the right tcp_flags" do 153 | # subject.tcp_flags.syn.should == 0 154 | # subject.tcp_flags.psh.should == 0 155 | # subject.tcp_flags.ack.should == 1 156 | # end 157 | # end 158 | 159 | # context "when generating reflective bpf stubs" do 160 | # before :each do 161 | # @ack_pkt = @helper.get_tcp_packet() 162 | # @ack_pkt.ip_saddr = "192.168.1.1" 163 | # @ack_pkt.ip_daddr = "192.168.1.2" 164 | # @ack_pkt.eth_saddr = "aa:aa:aa:aa:aa:aa" 165 | # @ack_pkt.eth_daddr = "bb:bb:bb:bb:bb:bb" 166 | # @ack_pkt.tcp_sport = 12345 167 | # @ack_pkt.tcp_dport = 80 168 | # @ack_pkt.tcp_flags.syn = 1 169 | # @ack_pkt.tcp_flags.ack = 1 170 | # @ack_pkt.tcp_seq = 11111 171 | # @ack_pkt.tcp_ack = 22222 172 | # end 173 | 174 | # it "should generate the right bpf" do 175 | # right_bpf = "src 192.168.1.2 and " + 176 | # "dst 192.168.1.1 and " + 177 | # "src port 80 and " + 178 | # "dst port 12345" 179 | 180 | # @helper.get_reflective_bpf(@ack_pkt).should == right_bpf 181 | # end 182 | # end 183 | 184 | end 185 | end 186 | 187 | -------------------------------------------------------------------------------- /spec/bnat/packet_factory_spec.rb: -------------------------------------------------------------------------------- 1 | #require 'spec_helper' 2 | #require 'rubygems' 3 | #require 'bnat' 4 | #require 'packetfu' 5 | 6 | #module Bnat 7 | # describe PacketFactory do 8 | 9 | # before :each do 10 | # @pf = Bnat::PacketFactory.new('eth0') 11 | # end 12 | 13 | # context "when initializing a bnat packet factory" do 14 | # subject{@pf} 15 | # its(:class) {should == Bnat::PacketFactory} 16 | # end 17 | 18 | # context "when getting a generic tcp packet" do 19 | # subject{@pf} 20 | 21 | # it "should be the right class" do 22 | # @pf.get_tcp_packet.class.should == PacketFu::TCPPacket 23 | # end 24 | # end 25 | 26 | # context "when getting a syn tcp packet" do 27 | # subject{@pf} 28 | 29 | # it "should be the right class" do 30 | # @pf.get_syn_probe.class.should == PacketFu::TCPPacket 31 | # end 32 | 33 | # it "should be a syn packet" do 34 | # @pf.get_syn_probe.tcp_flags.syn.should == 1 35 | # end 36 | # end 37 | 38 | # context "when getting 1000 syn tcp packets" do 39 | # before :each do 40 | # @packets = (1..1000).collect {@pf.get_syn_probe} 41 | # end 42 | 43 | # it "should generate one thousand packets" do 44 | # @packets.size.should == 1000 45 | # end 46 | 47 | # it "should generate uniq sequence number and source port pairs" do 48 | # @packets.collect {|p| [p.tcp_seq, p.tcp_seq]}.uniq.size.should == 1000 49 | # end 50 | 51 | # end 52 | 53 | # end 54 | #end 55 | -------------------------------------------------------------------------------- /spec/bnat/result_spec.rb: -------------------------------------------------------------------------------- 1 | require 'bnat' 2 | require 'rspec/its' 3 | 4 | module BNAT 5 | describe Result do 6 | before :each do 7 | @helper = class Helper 8 | include BNAT::Common 9 | end.new 10 | 11 | @request_pkt = @helper.get_tcp_packet 12 | @response_pkt = @helper.get_tcp_packet 13 | 14 | @result = BNAT::Result.new( 15 | @request_pkt, 16 | @response_pkt 17 | ) 18 | end 19 | 20 | subject{@result} 21 | 22 | it "should generate a hash result" do 23 | subject.to_hash.should be_kind_of(::Hash) 24 | end 25 | 26 | it "should generate a parsable JSON result" do 27 | JSON.parse(subject.to_json).should be_kind_of(::Hash) 28 | end 29 | 30 | it "should generate a parsable XML result" do 31 | Nokogiri::XML(subject.to_xml).should be_kind_of(Nokogiri::XML::Document) 32 | end 33 | end 34 | end -------------------------------------------------------------------------------- /spec/bnat/scanner_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'bnat' 3 | require 'packetfu' 4 | 5 | module BNAT 6 | describe Scanner do 7 | 8 | before :each do 9 | @opts = { 10 | :iface => "eth0", 11 | :ports => [80,443], 12 | :targets => [ 13 | "192.168.1.1", 14 | "192.168.1.2" 15 | ] 16 | } 17 | @scanner = BNAT::Scanner.new(@opts) 18 | end 19 | 20 | context "when initializing a scanner object" do 21 | subject{@scanner} 22 | 23 | its(:class) {should == BNAT::Scanner} 24 | its(:iface) {should == "eth0"} 25 | its(:ports) {should == [80,443]} 26 | its(:targets) {should == ["192.168.1.1", "192.168.1.2"]} 27 | end 28 | 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/bnat/tcp_packet_spec.rb: -------------------------------------------------------------------------------- 1 | require 'bnat' 2 | 3 | module BNAT 4 | describe TCPPacket do 5 | 6 | before :each do 7 | @syn_pkt = BNAT::TCPPacket.new() 8 | @syn_pkt.ip_saddr = "192.168.1.1" 9 | @syn_pkt.ip_daddr = "192.168.1.2" 10 | @syn_pkt.tcp_sport = 12345 11 | @syn_pkt.tcp_dport = 80 12 | @syn_pkt.tcp_seq = 10000 13 | @syn_pkt.tcp_ack = 0 14 | 15 | @syn_pkt_clone = @syn_pkt.dup 16 | end 17 | 18 | context "when initializing a bnat packet" do 19 | subject{@syn_pkt} 20 | 21 | its(:class) {should == BNAT::TCPPacket} 22 | its(:ip_saddr) {should == "192.168.1.1"} 23 | its(:ip_daddr) {should == "192.168.1.2"} 24 | its(:tcp_sport) {should == 12345} 25 | its(:tcp_dport) {should == 80} 26 | its(:tcp_seq) {should == 10000} 27 | its(:tcp_ack) {should == 0} 28 | end 29 | 30 | 31 | context "when checking the comparison methods against a copy" do 32 | subject{@syn_pkt} 33 | 34 | it "should be == to the clone" do 35 | subject.should == @syn_pkt_clone 36 | end 37 | 38 | it "should be eql? to the clone" do 39 | subject.eql?(@syn_pkt_clone).should == true 40 | end 41 | 42 | it "should not be ip_bnat?" do 43 | subject.ip_bnat?(@syn_pkt_clone).should == false 44 | end 45 | 46 | it "should not be port_bnat?" do 47 | subject.port_bnat?(@syn_pkt_clone).should == false 48 | end 49 | 50 | it "should not be ip_port_bnat?" do 51 | subject.ip_port_bnat?(@syn_pkt_clone).should == false 52 | end 53 | end 54 | 55 | context "when checking the comparison methods against ip_bnat" do 56 | before :each do 57 | @syn_ack_pkt = BNAT::TCPPacket.new() 58 | @syn_ack_pkt.ip_saddr = "192.168.1.3" 59 | @syn_ack_pkt.ip_daddr = "192.168.1.1" 60 | @syn_ack_pkt.tcp_sport = 80 61 | @syn_ack_pkt.tcp_dport = 12345 62 | @syn_ack_pkt.tcp_seq = 20000 63 | @syn_ack_pkt.tcp_ack = 10001 64 | end 65 | 66 | subject{@syn_pkt} 67 | 68 | it "should be ip_bnat?" do 69 | subject.ip_bnat?(@syn_ack_pkt).should == true 70 | end 71 | 72 | it "should not be port_bnat?" do 73 | subject.port_bnat?(@syn_ack_pkt).should == false 74 | end 75 | 76 | it "should not be ip_port_bnat?" do 77 | subject.ip_port_bnat?(@syn_ack_pkt).should == false 78 | end 79 | 80 | it "should not be ip_match?" do 81 | subject.ip_match?(@syn_ack_pkt).should == false 82 | end 83 | 84 | it "should be port_match?" do 85 | subject.port_match?(@syn_ack_pkt).should == true 86 | end 87 | 88 | it "should be ack_match?" do 89 | subject.ack_match?(@syn_ack_pkt).should == true 90 | end 91 | 92 | it "should not be src_ip_match?" do 93 | subject.src_ip_match?(@syn_ack_pkt).should == false 94 | end 95 | 96 | it "should be dst_ip_match?" do 97 | subject.dst_ip_match?(@syn_ack_pkt).should == true 98 | end 99 | 100 | it "should be src_port_match?" do 101 | subject.src_port_match?(@syn_ack_pkt).should == true 102 | end 103 | 104 | it "should be dst_port_match?" do 105 | subject.dst_port_match?(@syn_ack_pkt).should == true 106 | end 107 | 108 | end 109 | 110 | context "when checking the comparison methods against port_bnat" do 111 | before :each do 112 | @syn_ack_pkt = BNAT::TCPPacket.new() 113 | @syn_ack_pkt.ip_saddr = "192.168.1.2" 114 | @syn_ack_pkt.ip_daddr = "192.168.1.1" 115 | @syn_ack_pkt.tcp_sport = 81 116 | @syn_ack_pkt.tcp_dport = 12345 117 | @syn_ack_pkt.tcp_seq = 20000 118 | @syn_ack_pkt.tcp_ack = 10001 119 | end 120 | 121 | subject{@syn_pkt} 122 | 123 | it "should not be ip_bnat?" do 124 | subject.ip_bnat?(@syn_ack_pkt).should == false 125 | end 126 | 127 | it "should be port_bnat?" do 128 | subject.port_bnat?(@syn_ack_pkt).should == true 129 | end 130 | 131 | it "should not be ip_port_bnat?" do 132 | subject.ip_port_bnat?(@syn_ack_pkt).should == false 133 | end 134 | 135 | it "should be ip_match?" do 136 | subject.ip_match?(@syn_ack_pkt).should == true 137 | end 138 | 139 | it "should not be port_match?" do 140 | subject.port_match?(@syn_ack_pkt).should == false 141 | end 142 | 143 | it "should be ack_match?" do 144 | subject.ack_match?(@syn_ack_pkt).should == true 145 | end 146 | 147 | it "should be src_ip_match?" do 148 | subject.src_ip_match?(@syn_ack_pkt).should == true 149 | end 150 | 151 | it "should be dst_ip_match?" do 152 | subject.dst_ip_match?(@syn_ack_pkt).should == true 153 | end 154 | 155 | it "should not be src_port_match?" do 156 | subject.src_port_match?(@syn_ack_pkt).should == false 157 | end 158 | 159 | it "should be dst_port_match?" do 160 | subject.dst_port_match?(@syn_ack_pkt).should == true 161 | end 162 | 163 | end 164 | 165 | context "when checking the comparison methods against ip_port_bnat" do 166 | before :each do 167 | @syn_ack_pkt = BNAT::TCPPacket.new() 168 | @syn_ack_pkt.ip_saddr = "192.168.1.3" 169 | @syn_ack_pkt.ip_daddr = "192.168.1.1" 170 | @syn_ack_pkt.tcp_sport = 81 171 | @syn_ack_pkt.tcp_dport = 12345 172 | @syn_ack_pkt.tcp_seq = 20000 173 | @syn_ack_pkt.tcp_ack = 10001 174 | end 175 | 176 | subject{@syn_pkt} 177 | 178 | it "should not be ip_bnat?" do 179 | subject.ip_bnat?(@syn_ack_pkt).should == false 180 | end 181 | 182 | it "should not be port_bnat?" do 183 | subject.port_bnat?(@syn_ack_pkt).should == false 184 | end 185 | 186 | it "should be ip_port_bnat?" do 187 | subject.ip_port_bnat?(@syn_ack_pkt).should == true 188 | end 189 | 190 | it "should not be ip_match?" do 191 | subject.ip_match?(@syn_ack_pkt).should == false 192 | end 193 | 194 | it "should not be port_match?" do 195 | subject.port_match?(@syn_ack_pkt).should == false 196 | end 197 | 198 | it "should be ack_match?" do 199 | subject.ack_match?(@syn_ack_pkt).should == true 200 | end 201 | 202 | it "should be src_ip_match?" do 203 | subject.src_ip_match?(@syn_ack_pkt).should == false 204 | end 205 | 206 | it "should be dst_ip_match?" do 207 | subject.dst_ip_match?(@syn_ack_pkt).should == true 208 | end 209 | 210 | it "should not be src_port_match?" do 211 | subject.src_port_match?(@syn_ack_pkt).should == false 212 | end 213 | 214 | it "should be dst_port_match?" do 215 | subject.dst_port_match?(@syn_ack_pkt).should == true 216 | end 217 | 218 | end 219 | 220 | end 221 | end 222 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/its' 2 | 3 | # This file was generated by the `rspec --init` command. Conventionally, all 4 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 5 | # Require this file using `require "spec_helper"` to ensure that it is only 6 | # loaded once. 7 | # 8 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 9 | RSpec.configure do |config| 10 | config.treat_symbols_as_metadata_keys_with_true_values = true 11 | config.run_all_when_everything_filtered = true 12 | config.filter_run :focus 13 | 14 | # Run specs in random order to surface order dependencies. If you find an 15 | # order dependency and want to debug it, you can fix the order by providing 16 | # the seed, which is printed after each run. 17 | # --seed 1234 18 | config.order = 'random' 19 | end 20 | --------------------------------------------------------------------------------