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