├── .gitignore ├── .goreleaser.yml ├── LICENSE ├── Makefile ├── README.md ├── ip.go ├── main.go ├── probe_netbios.go └── utils.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # .goreleaser.yml 2 | # Build customization 3 | builds: 4 | - binary: nextnet 5 | goos: 6 | - linux 7 | - windows 8 | - darwin 9 | - freebsd 10 | goarch: 11 | - amd64 12 | - 386 13 | - mips 14 | - mipsle 15 | - mips64 16 | - mips64le 17 | - arm 18 | - arm64 19 | - ppc 20 | - ppc64 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2018, HD Moore 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ALL: 2 | @go get github.com/mitchellh/gox && \ 3 | go get -u ./... && \ 4 | go fmt ./... && \ 5 | go vet ./... && \ 6 | go build ./... && \ 7 | go install ./... 8 | 9 | 10 | .PHONY: ALL 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | nextnet 2 | === 3 | 4 | [![GoDoc](https://godoc.org/github.com/hdm/nextnet?status.svg)](https://godoc.org/github.com/hdm/nextnet) 5 | 6 | nextnet is a pivot point discovery tool written in Go. 7 | 8 | ## Download 9 | 10 | Binary packages are available [here](https://github.com/hdm/nextnet/releases/latest). 11 | 12 | ## Install 13 | 14 | Most folks should download a compiled binary from the releases page. If you have Go installed, you can build your own binary by running: 15 | ``` 16 | $ go get github.com/hdm/nextnet 17 | ``` 18 | 19 | ## Usage 20 | 21 | ``` 22 | Usage: ./nextnet [options] ... 23 | 24 | Probes a list of networks for potential pivot points. 25 | 26 | Options: 27 | 28 | -h, --help Show Usage and exit. 29 | 30 | -rate int 31 | Set the maximum packets per second rate (default 1000) 32 | 33 | -version 34 | Show the application version 35 | ``` 36 | 37 | ### Local Network Discovery 38 | Identify a multi-homed Windows desktop on the local network. 39 | 40 | ``` 41 | $ nextnet 192.168.0.0/24 42 | 43 | {"host":"192.168.0.112","port":"137","proto":"udp","probe":"netbios","name":"DESKTOP-H14GTIO","nets":["192.168.10.12","192.168.20.12"],"info":{"domain":"WORKGROUP","hwaddr":"14:dd:a9:e4:10:a0"}} 44 | 45 | ``` 46 | 47 | ### Fast External Network Scans 48 | Quickly identify multi-homed hosts running Netbios on the internet 49 | 50 | ``` 51 | $ nextnet -rate 10000 114.80.0.0/16 | grep nets 52 | 53 | {"host":"114.80.62.194","port":"137","proto":"udp","probe":"netbios","name":"WIN-6F47E00F5JS","nets":["192.168.80.2","192.168.90.8"],"info":{"domain":"WORKGROUP","hwaddr":"b8:2a:72:d6:e6:b7"}} 54 | {"host":"114.80.60.40","port":"137","proto":"udp","probe":"netbios","name":"ESX140-2008G","nets":["192.168.11.40","114.80.60.40"],"info":{"domain":"WORKGROUP","hwaddr":"00:0c:29:03:df:5a"}} 55 | {"host":"114.80.86.222","port":"137","proto":"udp","probe":"netbios","name":"SHOFFICE-ISA","nets":["114.80.86.222"],"info":{"domain":"\u0001\u0002__MSBROWSE__\u0002","hwaddr":"00:0c:29:5f:ba:d1"}} 56 | {"host":"114.80.153.49","port":"137","proto":"udp","probe":"netbios","name":"WIN-0GRAGSOGFGS","nets":["114.80.153.49","172.16.0.157"],"info":{"domain":"WORKGROUP","hwaddr":"90:b1:1c:40:72:31"}} 57 | {"host":"114.80.156.143","port":"137","proto":"udp","probe":"netbios","name":"WIN-E1GEEJQBT45","nets":["114.80.156.143","192.168.60.1"],"info":{"domain":"WORKGROUP","hwaddr":"b0:83:fe:e9:3b:d0"}} 58 | {"host":"114.80.157.110","port":"137","proto":"udp","probe":"netbios","name":"SHWGQ-DBWEB","nets":["112.65.248.9"],"info":{"domain":"WORKGROUP","hwaddr":"00:26:55:1e:8c:04"}} 59 | {"host":"114.80.157.108","port":"137","proto":"udp","probe":"netbios","name":"DATA-FCMB","nets":["112.65.248.8"],"info":{"domain":"WORKGROUP","hwaddr":"00:1f:29:64:e5:f4"}} 60 | {"host":"114.80.157.170","port":"137","proto":"udp","probe":"netbios","name":"DZH-TRS6","nets":["10.10.2.13"],"info":{"domain":"WORKGROUP","hwaddr":"ac:16:2d:7a:ff:f0"}} 61 | {"host":"114.80.157.44","port":"137","proto":"udp","probe":"netbios","name":"WINAD2","nets":["169.254.35.57","114.80.157.44"],"info":{"domain":"WINAD02","hwaddr":"00:50:56:9d:4f:e1"}} 62 | {"host":"114.80.156.99","port":"137","proto":"udp","probe":"netbios","name":"WUHAN","nets":["10.1.1.2","169.254.95.120"],"info":{"domain":"WORKGROUP","hwaddr":"34:40:b5:9e:cf:28"}} 63 | {"host":"114.80.166.28","port":"137","proto":"udp","probe":"netbios","name":"KEDE-MOBILE-131","nets":["114.80.166.28"],"info":{"domain":"WORKGROUP","hwaddr":"00:50:56:94:54:5c"}} 64 | {"host":"114.80.167.219","port":"137","proto":"udp","probe":"netbios","name":"WIN-DB2BC7UU0CM","nets":["192.168.100.191"],"info":{"domain":"WORKGROUP","hwaddr":"00:50:56:94:49:7c"}} 65 | {"host":"114.80.157.135","port":"137","proto":"udp","probe":"netbios","name":"WIN-L3DFEEB","nets":["169.254.54.216"],"info":{"domain":"WORKGROUP","hwaddr":"90:b1:1c:09:61:cc"}} 66 | {"host":"114.80.207.27","port":"137","proto":"udp","probe":"netbios","name":"R420","nets":["192.168.126.1","192.168.72.1","169.254.73.205"],"info":{"domain":"WORKGROUP","hwaddr":"44:a8:42:3f:8a:23"}} 67 | {"host":"114.80.215.81","port":"137","proto":"udp","probe":"netbios","name":"WINDOWS-7III9JS","nets":["114.80.215.81","192.168.108.1","192.168.233.1"],"info":{"domain":"WORKGROUP","hwaddr":"6c:ae:8b:38:51:f3"}} 68 | {"host":"114.80.215.90","port":"137","proto":"udp","probe":"netbios","name":"HYDROGEN","nets":["114.80.215.90","2.0.1.1","1.1.1.1","192.168.118.1"],"info":{"domain":"WORKGROUP","hwaddr":"e4:1f:13:95:ee:c2"}} 69 | {"host":"114.80.222.219","port":"137","proto":"udp","probe":"netbios","name":"WIN-VINFEGJ7HP8","nets":["114.80.222.219"],"info":{"domain":"WORKGROUP","hwaddr":"14:18:77:41:17:25"}} 70 | {"host":"114.80.245.193","port":"137","proto":"udp","probe":"netbios","name":"WINDOWS-OT2WS9T","nets":["114.80.245.193"],"info":{"domain":"WORKGROUP","hwaddr":"a0:d3:c1:f2:3e:26"}} 71 | ``` 72 | -------------------------------------------------------------------------------- /ip.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "math" 8 | "net" 9 | "os" 10 | "regexp" 11 | "strings" 12 | ) 13 | 14 | var Match_IPv6 = regexp.MustCompile(`^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?$`) 15 | 16 | var Match_IPv4 = regexp.MustCompile(`^(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))$`) 17 | 18 | var IPv4_Masks = map[uint32]uint32{ 19 | 1: 32, 20 | 2: 31, 21 | 4: 30, 22 | 8: 29, 23 | 16: 28, 24 | 32: 27, 25 | 64: 26, 26 | 128: 25, 27 | 256: 24, 28 | 512: 23, 29 | 1024: 22, 30 | 2048: 21, 31 | 4096: 20, 32 | 8192: 19, 33 | 16384: 18, 34 | 32768: 17, 35 | 65536: 16, 36 | 131072: 15, 37 | 262144: 14, 38 | 524288: 13, 39 | 1048576: 12, 40 | 2097152: 11, 41 | 4194304: 10, 42 | 8388608: 9, 43 | 16777216: 8, 44 | 33554432: 7, 45 | 67108864: 6, 46 | 134217728: 5, 47 | 268435456: 4, 48 | 536870912: 3, 49 | 1073741824: 2, 50 | 2147483648: 1, 51 | } 52 | 53 | var IPv4_Mask_Sizes = []uint32{ 54 | 2147483648, 55 | 1073741824, 56 | 536870912, 57 | 268435456, 58 | 134217728, 59 | 67108864, 60 | 33554432, 61 | 16777216, 62 | 8388608, 63 | 4194304, 64 | 2097152, 65 | 1048576, 66 | 524288, 67 | 262144, 68 | 131072, 69 | 65536, 70 | 32768, 71 | 16384, 72 | 8192, 73 | 4096, 74 | 2048, 75 | 1024, 76 | 512, 77 | 256, 78 | 128, 79 | 64, 80 | 32, 81 | 16, 82 | 8, 83 | 4, 84 | 2, 85 | 1, 86 | } 87 | 88 | func IPv4_to_UInt(ips string) (uint32, error) { 89 | ip := net.ParseIP(ips) 90 | if ip == nil { 91 | return 0, errors.New("Invalid IPv4 address") 92 | } 93 | ip = ip.To4() 94 | return binary.BigEndian.Uint32(ip), nil 95 | } 96 | 97 | func UInt_to_IPv4(ipi uint32) string { 98 | ipb := make([]byte, 4) 99 | binary.BigEndian.PutUint32(ipb, ipi) 100 | ip := net.IP(ipb) 101 | return ip.String() 102 | } 103 | 104 | func IPv4Range2CIDRs(s_ip string, e_ip string) ([]string, error) { 105 | 106 | s_i, s_e := IPv4_to_UInt(s_ip) 107 | if s_e != nil { 108 | return []string{}, s_e 109 | } 110 | 111 | e_i, e_e := IPv4_to_UInt(e_ip) 112 | if e_e != nil { 113 | return []string{}, e_e 114 | } 115 | 116 | if s_i > e_i { 117 | return []string{}, errors.New("Start address is bigger than end address") 118 | } 119 | 120 | return IPv4UIntRange2CIDRs(s_i, e_i), nil 121 | } 122 | 123 | func IPv4UIntRange2CIDRs(s_i uint32, e_i uint32) []string { 124 | cidrs := []string{} 125 | 126 | // Ranges are inclusive 127 | size := e_i - s_i + 1 128 | 129 | if size == 0 { 130 | return cidrs 131 | } 132 | 133 | for i := range IPv4_Mask_Sizes { 134 | 135 | mask_size := IPv4_Mask_Sizes[i] 136 | 137 | if mask_size > size { 138 | continue 139 | } 140 | 141 | // Exact match of the block size 142 | if mask_size == size { 143 | cidrs = append(cidrs, fmt.Sprintf("%s/%d", UInt_to_IPv4(s_i), IPv4_Masks[mask_size])) 144 | break 145 | } 146 | 147 | // Chop off the biggest block that fits 148 | cidrs = append(cidrs, fmt.Sprintf("%s/%d", UInt_to_IPv4(s_i), IPv4_Masks[mask_size])) 149 | s_i = s_i + mask_size 150 | 151 | // Look for additional blocks 152 | new_cidrs := IPv4UIntRange2CIDRs(s_i, e_i) 153 | 154 | // Merge those blocks into out results 155 | for x := range new_cidrs { 156 | cidrs = append(cidrs, new_cidrs[x]) 157 | } 158 | break 159 | 160 | } 161 | return cidrs 162 | } 163 | 164 | func AddressesFromCIDR(cidr string, o chan<- string) { 165 | if len(cidr) == 0 { 166 | return 167 | } 168 | 169 | // We may receive bare IP addresses, not CIDRs sometimes 170 | if !strings.Contains(cidr, "/") { 171 | if strings.Contains(cidr, ":") { 172 | cidr = cidr + "/128" 173 | } else { 174 | cidr = cidr + "/32" 175 | } 176 | } 177 | 178 | // Parse CIDR into base address + mask 179 | ip, net, err := net.ParseCIDR(cidr) 180 | if err != nil { 181 | fmt.Fprintf(os.Stderr, "Invalid CIDR %s: %s\n", cidr, err.Error()) 182 | return 183 | } 184 | 185 | // Verify IPv4 for now 186 | ip4 := net.IP.To4() 187 | if ip4 == nil { 188 | fmt.Fprintf(os.Stderr, "Invalid IPv4 CIDR %s\n", cidr) 189 | return 190 | } 191 | 192 | net_base, err := IPv4_to_UInt(net.IP.String()) 193 | if err != nil { 194 | fmt.Fprintf(os.Stderr, "Invalid IPv4 Address %s: %s\n", ip.String(), err.Error()) 195 | return 196 | } 197 | 198 | mask_ones, mask_total := net.Mask.Size() 199 | 200 | // Does not work for IPv6 due to cast to uint32 201 | net_size := uint32(math.Pow(2, float64(mask_total-mask_ones))) 202 | 203 | cur_base := net_base 204 | end_base := net_base + net_size 205 | cur_addr := cur_base 206 | 207 | for cur_addr = cur_base; cur_addr < end_base; cur_addr++ { 208 | o <- UInt_to_IPv4(cur_addr) 209 | } 210 | 211 | return 212 | } 213 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "golang.org/x/time/rate" 8 | "os" 9 | "runtime" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | type ScanResult struct { 15 | Host string `json:"host"` 16 | Port string `json:"port,omitempty"` 17 | Proto string `json:"proto,omitempty"` 18 | Probe string `json:"probe,omitempty"` 19 | Name string `json:"name,omitempty"` 20 | Nets []string `json:"nets,omitempty"` 21 | Info map[string]string `json:"info"` 22 | } 23 | 24 | type Prober interface { 25 | Setup() 26 | Initialize() 27 | Wait() 28 | AddTarget(string) 29 | CloseInput() 30 | SetOutput(chan<- ScanResult) 31 | CheckRateLimit() 32 | SetLimiter(*rate.Limiter) 33 | } 34 | 35 | type Probe struct { 36 | name string 37 | options map[string]string 38 | waiter sync.WaitGroup 39 | input chan string 40 | output chan<- ScanResult 41 | limiter *rate.Limiter 42 | } 43 | 44 | func (this *Probe) String() string { 45 | return fmt.Sprintf("%s", this.name) 46 | } 47 | 48 | func (this *Probe) Wait() { 49 | this.waiter.Wait() 50 | return 51 | } 52 | 53 | func (this *Probe) Setup() { 54 | this.name = "generic" 55 | this.input = make(chan string) 56 | return 57 | } 58 | 59 | func (this *Probe) Initialize() { 60 | this.Setup() 61 | this.name = "generic" 62 | return 63 | } 64 | 65 | func (this *Probe) SetOutput(c_out chan<- ScanResult) { 66 | this.output = c_out 67 | return 68 | } 69 | 70 | func (this *Probe) AddTarget(t string) { 71 | this.input <- t 72 | return 73 | } 74 | 75 | func (this *Probe) CloseInput() { 76 | close(this.input) 77 | return 78 | } 79 | 80 | func (this *Probe) SetLimiter(limiter *rate.Limiter) { 81 | this.limiter = limiter 82 | return 83 | } 84 | 85 | func (this *Probe) CheckRateLimit() { 86 | for this.limiter.Allow() == false { 87 | time.Sleep(10 * time.Millisecond) 88 | } 89 | } 90 | 91 | var limiter *rate.Limiter 92 | var ppsrate *int 93 | var probes []Prober 94 | var wi sync.WaitGroup 95 | var wo sync.WaitGroup 96 | 97 | func usage() { 98 | fmt.Println("Usage: " + os.Args[0] + " [cidr] ... [cidr]") 99 | fmt.Println("") 100 | fmt.Println("Probes a list of networks for potential pivot points.") 101 | fmt.Println("") 102 | fmt.Println("Options:") 103 | flag.PrintDefaults() 104 | } 105 | 106 | func outputWriter(o <-chan ScanResult) { 107 | 108 | for found := range o { 109 | j, err := json.Marshal(found) 110 | if err != nil { 111 | fmt.Fprintf(os.Stderr, "Error marshaling result: '%v' : %s\n", found, err) 112 | continue 113 | } 114 | os.Stdout.Write(j) 115 | os.Stdout.Write([]byte("\n")) 116 | } 117 | wo.Done() 118 | } 119 | 120 | func initializeProbes(c_out chan<- ScanResult) { 121 | for _, probe := range probes { 122 | probe.Initialize() 123 | probe.SetOutput(c_out) 124 | probe.SetLimiter(limiter) 125 | } 126 | } 127 | 128 | func waitProbes() { 129 | for _, probe := range probes { 130 | probe.Wait() 131 | } 132 | } 133 | 134 | func processAddress(i <-chan string, o chan<- ScanResult) { 135 | for addr := range i { 136 | for _, probe := range probes { 137 | probe.AddTarget(addr) 138 | } 139 | } 140 | 141 | for _, probe := range probes { 142 | probe.CloseInput() 143 | } 144 | wi.Done() 145 | } 146 | 147 | func main() { 148 | 149 | runtime.GOMAXPROCS(runtime.NumCPU()) 150 | 151 | flag.Usage = func() { usage() } 152 | version := flag.Bool("version", false, "Show the application version") 153 | ppsrate = flag.Int("rate", 1000, "Set the maximum packets per second rate") 154 | 155 | flag.Parse() 156 | 157 | if *version { 158 | PrintVersion("nextnet") 159 | os.Exit(0) 160 | } 161 | 162 | limiter = rate.NewLimiter(rate.Limit(*ppsrate), *ppsrate*3) 163 | 164 | // Input addresses 165 | c_addr := make(chan string) 166 | 167 | // Output structs 168 | c_out := make(chan ScanResult) 169 | 170 | // Configure the probes 171 | initializeProbes(c_out) 172 | 173 | // Launch a single input address processor 174 | wi.Add(1) 175 | go processAddress(c_addr, c_out) 176 | 177 | // Launch a single output writer 178 | wo.Add(1) 179 | go outputWriter(c_out) 180 | 181 | // Parse CIDRs and feed IPs to the input channel 182 | for _, cidr := range flag.Args() { 183 | AddressesFromCIDR(cidr, c_addr) 184 | } 185 | 186 | // Close the cidr input channel 187 | close(c_addr) 188 | 189 | // Wait for the input feed to complete 190 | wi.Wait() 191 | 192 | // Wait for pending probes 193 | waitProbes() 194 | 195 | // Close the output handle 196 | close(c_out) 197 | 198 | // Wait for the output goroutine 199 | wo.Wait() 200 | } 201 | -------------------------------------------------------------------------------- /probe_netbios.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "log" 8 | "math/rand" 9 | "net" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | const MaxPendingReplies int = 256 15 | const MaxProbeResponseTime time.Duration = time.Second * 2 16 | 17 | type NetbiosInfo struct { 18 | statusRecv time.Time 19 | nameSent time.Time 20 | nameRecv time.Time 21 | statusReply NetbiosReplyStatus 22 | nameReply NetbiosReplyStatus 23 | } 24 | 25 | type ProbeNetbios struct { 26 | Probe 27 | socket net.PacketConn 28 | replies map[string]*NetbiosInfo 29 | } 30 | 31 | type NetbiosReplyHeader struct { 32 | XID uint16 33 | Flags uint16 34 | QuestionCount uint16 35 | AnswerCount uint16 36 | AuthCount uint16 37 | AdditionalCount uint16 38 | QuestionName [34]byte 39 | RecordType uint16 40 | RecordClass uint16 41 | RecordTTL uint32 42 | RecordLength uint16 43 | } 44 | 45 | type NetbiosReplyName struct { 46 | Name [15]byte 47 | Type uint8 48 | Flag uint16 49 | } 50 | 51 | type NetbiosReplyAddress struct { 52 | Flag uint16 53 | Address [4]uint8 54 | } 55 | 56 | type NetbiosReplyStatus struct { 57 | Header NetbiosReplyHeader 58 | HostName [15]byte 59 | UserName [15]byte 60 | Names []NetbiosReplyName 61 | Addresses []NetbiosReplyAddress 62 | HWAddr string 63 | } 64 | 65 | func (this *ProbeNetbios) ProcessReplies() { 66 | buff := make([]byte, 1500) 67 | 68 | this.replies = make(map[string]*NetbiosInfo) 69 | 70 | for { 71 | rlen, raddr, rerr := this.socket.ReadFrom(buff) 72 | if rerr != nil { 73 | if nerr, ok := rerr.(net.Error); ok && nerr.Timeout() { 74 | log.Printf("probe %s receiver timed out: %s", this, rerr) 75 | continue 76 | } 77 | 78 | // Complain about other error types 79 | log.Printf("probe %s receiver returned error: %s", this, rerr) 80 | return 81 | } 82 | 83 | ip := raddr.(*net.UDPAddr).IP.String() 84 | 85 | reply := this.ParseReply(buff[0 : rlen-1]) 86 | if len(reply.Names) == 0 && len(reply.Addresses) == 0 { 87 | continue 88 | } 89 | 90 | _, found := this.replies[ip] 91 | if !found { 92 | nbinfo := new(NetbiosInfo) 93 | this.replies[ip] = nbinfo 94 | } 95 | 96 | // Handle status replies by sending a name request 97 | if reply.Header.RecordType == 0x21 { 98 | // log.Printf("probe %s received a status reply of %d bytes from %s", this, rlen, raddr) 99 | this.replies[ip].statusReply = reply 100 | this.replies[ip].statusRecv = time.Now() 101 | 102 | ntime := time.Time{} 103 | if this.replies[ip].nameSent == ntime { 104 | this.replies[ip].nameSent = time.Now() 105 | this.SendNameRequest(ip) 106 | } 107 | } 108 | 109 | // Handle name replies by reporting the result 110 | if reply.Header.RecordType == 0x20 { 111 | // log.Printf("probe %s received a name reply of %d bytes from %s", this, rlen, raddr) 112 | this.replies[ip].nameReply = reply 113 | this.replies[ip].nameRecv = time.Now() 114 | this.ReportResult(ip) 115 | } 116 | } 117 | } 118 | 119 | func (this *ProbeNetbios) SendRequest(ip string, req []byte) { 120 | addr, aerr := net.ResolveUDPAddr("udp", ip+":137") 121 | if aerr != nil { 122 | log.Printf("probe %s failed to resolve %s (%s)", this, ip, aerr) 123 | return 124 | } 125 | 126 | // Retry in case of network buffer congestion 127 | wcnt := 0 128 | for wcnt = 0; wcnt < 5; wcnt++ { 129 | 130 | this.CheckRateLimit() 131 | 132 | _, werr := this.socket.WriteTo(req, addr) 133 | if werr != nil { 134 | log.Printf("probe %s [%d/%d] failed to send to %s (%s)", this, wcnt+1, 5, ip, werr) 135 | time.Sleep(100 * time.Millisecond) 136 | continue 137 | } 138 | break 139 | } 140 | 141 | // Were we able to send it eventually? 142 | if wcnt == 5 { 143 | log.Printf("probe %s [%d/%d] gave up sending to %s", this, wcnt, 5, ip) 144 | } 145 | } 146 | 147 | func (this *ProbeNetbios) SendStatusRequest(ip string) { 148 | // log.Printf("probe %s is sending a status request to %s", this, ip) 149 | this.SendRequest(ip, this.CreateStatusRequest()) 150 | } 151 | 152 | func (this *ProbeNetbios) SendNameRequest(ip string) { 153 | sreply := this.replies[ip].statusReply 154 | name := TrimName(string(sreply.HostName[:])) 155 | this.SendRequest(ip, this.CreateNameRequest(name)) 156 | } 157 | 158 | func (this *ProbeNetbios) ResultFromIP(ip string) ScanResult { 159 | sreply := this.replies[ip].statusReply 160 | nreply := this.replies[ip].nameReply 161 | 162 | res := ScanResult{ 163 | Host: ip, 164 | Port: "137", 165 | Proto: "udp", 166 | Probe: this.String(), 167 | } 168 | 169 | res.Info = make(map[string]string) 170 | 171 | res.Name = TrimName(string(sreply.HostName[:])) 172 | 173 | if nreply.Header.RecordType == 0x20 { 174 | for _, ainfo := range nreply.Addresses { 175 | 176 | net := fmt.Sprintf("%d.%d.%d.%d", ainfo.Address[0], ainfo.Address[1], ainfo.Address[2], ainfo.Address[3]) 177 | if net == "0.0.0.0" { 178 | continue 179 | } 180 | 181 | res.Nets = append(res.Nets, net) 182 | } 183 | } 184 | 185 | if sreply.HWAddr != "00:00:00:00:00:00" { 186 | res.Info["hwaddr"] = sreply.HWAddr 187 | } 188 | 189 | username := TrimName(string(sreply.UserName[:])) 190 | if len(username) > 0 && username != res.Name { 191 | res.Info["username"] = username 192 | } 193 | 194 | for _, rname := range sreply.Names { 195 | 196 | tname := TrimName(string(rname.Name[:])) 197 | if tname == res.Name { 198 | continue 199 | } 200 | 201 | if rname.Flag&0x0800 != 0 { 202 | continue 203 | } 204 | 205 | res.Info["domain"] = tname 206 | } 207 | 208 | return res 209 | } 210 | 211 | func (this *ProbeNetbios) ReportResult(ip string) { 212 | this.output <- this.ResultFromIP(ip) 213 | delete(this.replies, ip) 214 | } 215 | 216 | func (this *ProbeNetbios) ReportIncompleteResults() { 217 | for ip, _ := range this.replies { 218 | this.ReportResult(ip) 219 | } 220 | } 221 | 222 | func (this *ProbeNetbios) EncodeNetbiosName(name [16]byte) [32]byte { 223 | encoded := [32]byte{} 224 | 225 | for i := 0; i < 16; i++ { 226 | if name[i] == 0 { 227 | encoded[(i*2)+0] = 'C' 228 | encoded[(i*2)+1] = 'A' 229 | } else { 230 | encoded[(i*2)+0] = byte((name[i] / 16) + 0x41) 231 | encoded[(i*2)+1] = byte((name[i] % 16) + 0x41) 232 | } 233 | } 234 | 235 | return encoded 236 | } 237 | 238 | func (this *ProbeNetbios) DecodeNetbiosName(name [32]byte) [16]byte { 239 | decoded := [16]byte{} 240 | 241 | for i := 0; i < 16; i++ { 242 | if name[(i*2)+0] == 'C' && name[(i*2)+1] == 'A' { 243 | decoded[i] = 0 244 | } else { 245 | decoded[i] = ((name[(i*2)+0] * 16) - 0x41) + (name[(i*2)+1] - 0x41) 246 | } 247 | } 248 | return decoded 249 | } 250 | 251 | func (this *ProbeNetbios) ParseReply(buff []byte) NetbiosReplyStatus { 252 | 253 | resp := NetbiosReplyStatus{} 254 | temp := bytes.NewBuffer(buff) 255 | 256 | binary.Read(temp, binary.BigEndian, &resp.Header) 257 | 258 | if resp.Header.QuestionCount != 0 { 259 | return resp 260 | } 261 | 262 | if resp.Header.AnswerCount == 0 { 263 | return resp 264 | } 265 | 266 | // Names 267 | if resp.Header.RecordType == 0x21 { 268 | var rcnt uint8 269 | var ridx uint8 270 | binary.Read(temp, binary.BigEndian, &rcnt) 271 | 272 | for ridx = 0; ridx < rcnt; ridx++ { 273 | name := NetbiosReplyName{} 274 | binary.Read(temp, binary.BigEndian, &name) 275 | resp.Names = append(resp.Names, name) 276 | 277 | if name.Type == 0x20 { 278 | resp.HostName = name.Name 279 | } 280 | 281 | if name.Type == 0x03 { 282 | resp.UserName = name.Name 283 | } 284 | } 285 | 286 | var hwbytes [6]uint8 287 | binary.Read(temp, binary.BigEndian, &hwbytes) 288 | resp.HWAddr = fmt.Sprintf("%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", 289 | hwbytes[0], hwbytes[1], hwbytes[2], hwbytes[3], hwbytes[4], hwbytes[5], 290 | ) 291 | return resp 292 | } 293 | 294 | // Addresses 295 | if resp.Header.RecordType == 0x20 { 296 | var ridx uint16 297 | for ridx = 0; ridx < (resp.Header.RecordLength / 6); ridx++ { 298 | addr := NetbiosReplyAddress{} 299 | binary.Read(temp, binary.BigEndian, &addr) 300 | resp.Addresses = append(resp.Addresses, addr) 301 | } 302 | } 303 | 304 | return resp 305 | } 306 | 307 | func (this *ProbeNetbios) CreateStatusRequest() []byte { 308 | return []byte{ 309 | byte(rand.Intn(256)), byte(rand.Intn(256)), 310 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 311 | 0x00, 0x00, 0x20, 0x43, 0x4b, 0x41, 0x41, 0x41, 312 | 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 313 | 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 314 | 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 315 | 0x41, 0x41, 0x41, 0x00, 0x00, 0x21, 0x00, 0x01, 316 | } 317 | } 318 | 319 | func (this *ProbeNetbios) CreateNameRequest(name string) []byte { 320 | nbytes := [16]byte{} 321 | copy(nbytes[0:15], []byte(strings.ToUpper(name)[:])) 322 | 323 | req := []byte{ 324 | byte(rand.Intn(256)), byte(rand.Intn(256)), 325 | 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 326 | 0x00, 0x00, 0x20, 327 | 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 328 | 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 329 | 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 330 | 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 331 | 0x00, 0x00, 0x20, 0x00, 0x01, 332 | } 333 | 334 | encoded := this.EncodeNetbiosName(nbytes) 335 | copy(req[13:45], encoded[0:32]) 336 | return req 337 | } 338 | 339 | func (this *ProbeNetbios) Initialize() { 340 | this.Setup() 341 | this.name = "netbios" 342 | this.waiter.Add(1) 343 | 344 | // Open socket 345 | this.socket, _ = net.ListenPacket("udp", "") 346 | 347 | go func() { 348 | go this.ProcessReplies() 349 | 350 | for dip := range this.input { 351 | this.SendStatusRequest(dip) 352 | 353 | // If our pending replies gets > MAX, stop, process, report, clear, resume 354 | if len(this.replies) > MaxPendingReplies { 355 | log.Printf("probe %s is flushing due to maximum replies hit (%d)", this, len(this.replies)) 356 | time.Sleep(MaxProbeResponseTime) 357 | this.ReportIncompleteResults() 358 | } 359 | } 360 | 361 | // Sleep for packet timeout of initial probe 362 | log.Printf("probe %s is waiting for final replies to status probe", this) 363 | time.Sleep(MaxProbeResponseTime) 364 | 365 | // The receiver is sending interface probes in response to status probes 366 | 367 | log.Printf("probe %s is waiting for final replies to interface probe", this) 368 | time.Sleep(MaxProbeResponseTime) 369 | 370 | // Shut down receiver 371 | this.socket.Close() 372 | 373 | // Report any incomplete results (status reply but no name replies) 374 | this.ReportIncompleteResults() 375 | 376 | // Complete 377 | this.waiter.Done() 378 | }() 379 | 380 | return 381 | } 382 | 383 | func init() { 384 | probes = append(probes, new(ProbeNetbios)) 385 | } 386 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | func PrintVersion(app string) { 10 | var version = "master" 11 | fmt.Fprintf(os.Stderr, "%s v%s\n", app, version) 12 | } 13 | 14 | func TrimName(name string) string { 15 | return strings.TrimSpace(strings.Replace(name, "\x00", "", -1)) 16 | } 17 | --------------------------------------------------------------------------------