Skip to content

Commit cf5eab5

Browse files
committed
ICMP scan
1 parent db28814 commit cf5eab5

File tree

10 files changed

+1117
-85
lines changed

10 files changed

+1117
-85
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ The goal of this project is to create the fastest network scanner with clean and
1010
## Features
1111

1212
* **ARP scan**: Scan your local networks to detect live devices
13+
* **ICMP scan**: Use advanced ICMP scanning techniques to detect live hosts and firewall rules
1314
* **TCP SYN scan**: Traditional half-open scan to find open TCP ports
1415
* **TCP FIN / NULL / Xmas scans**: Scan techniques to bypass some firewall rules
1516
* **Custom TCP scans with any TCP flags**: Send whatever exotic packets you want and get a result with all the TCP flags set in the reply packet
@@ -388,6 +389,17 @@ In this case only ip addresses will be taken from the file and the **port** fiel
388389
./sx help
389390
```
390391

392+
## Reference
393+
394+
* **Network Security Assessment: Know Your Network 1st Edition** by Chris McNab
395+
* **ICMP Usage in Scanning - The Complete Know-How** by Ofir Arkin
396+
* [Transmission Control Protocol ( rfc793 )](https://tools.ietf.org/rfc/rfc793.txt)
397+
* [User Datagram Protocol ( rfc768 )](https://tools.ietf.org/rfc/rfc768.txt)
398+
* [Requirements for Internet Hosts -- Communication Layers ( rfc1122 )](https://tools.ietf.org/rfc/rfc1122.txt)
399+
* [SOCKS Protocol Version 5 ( rfc1928 )](https://tools.ietf.org/rfc/rfc1928.txt)
400+
* [Internet Control Message Protocol ( rfc792 )](https://tools.ietf.org/rfc/rfc792.txt)
401+
402+
391403
## License
392404

393405
This project is licensed under the MIT License. See the [LICENSE](https://github.com/v-byte-cpu/sx/blob/master/LICENSE) file for the full license text.

command/icmp.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package command
2+
3+
import (
4+
"context"
5+
"errors"
6+
"os"
7+
"os/signal"
8+
"runtime"
9+
"strconv"
10+
"strings"
11+
12+
"github.com/spf13/cobra"
13+
"github.com/v-byte-cpu/sx/pkg/scan"
14+
"github.com/v-byte-cpu/sx/pkg/scan/arp"
15+
"github.com/v-byte-cpu/sx/pkg/scan/icmp"
16+
)
17+
18+
var (
19+
cliICMPTypeFlag string
20+
cliICMPCodeFlag string
21+
cliICMPPayloadFlag string
22+
23+
cliICMPType uint8
24+
cliICMPCode uint8
25+
cliICMPPayload []byte
26+
)
27+
28+
func init() {
29+
icmpCmd.Flags().StringVar(&cliIPTTLFlag, "ttl", "",
30+
strings.Join([]string{"set IP TTL field of generated packet", "64 by default"}, "\n"))
31+
icmpCmd.Flags().StringVar(&cliIPTotalLenFlag, "iplen", "",
32+
strings.Join([]string{"set IP Total Length field of generated packet", "calculated by default"}, "\n"))
33+
icmpCmd.Flags().StringVar(&cliIPProtocolFlag, "ipproto", "",
34+
strings.Join([]string{"set IP Protocol field of generated packet", "1 (ICMP) by default"}, "\n"))
35+
icmpCmd.Flags().StringVar(&cliIPFlagsFlag, "ipflags", "",
36+
strings.Join([]string{"set IP Flags field of generated packet", "DF by default"}, "\n"))
37+
38+
icmpCmd.Flags().StringVarP(&cliICMPTypeFlag, "type", "t", "",
39+
strings.Join([]string{"set ICMP type of generated packet", "ICMP Echo (Type 8) by default"}, "\n"))
40+
icmpCmd.Flags().StringVarP(&cliICMPCodeFlag, "code", "c", "",
41+
strings.Join([]string{"set ICMP code of generated packet", "0 by default"}, "\n"))
42+
icmpCmd.Flags().StringVarP(&cliICMPPayloadFlag, "payload", "p", "",
43+
strings.Join([]string{"set byte payload of generated packet", "48 random bytes by default"}, "\n"))
44+
45+
icmpCmd.Flags().StringVarP(&cliARPCacheFileFlag, "arp-cache", "a", "",
46+
strings.Join([]string{"set ARP cache file", "reads from stdin by default"}, "\n"))
47+
rootCmd.AddCommand(icmpCmd)
48+
}
49+
50+
var icmpCmd = &cobra.Command{
51+
Use: "icmp [flags] subnet",
52+
Example: strings.Join([]string{
53+
"icmp 192.168.0.1/24",
54+
"icmp --ttl 37 192.168.0.1/24",
55+
"icmp --ipproto 157 192.168.0.1/24",
56+
`icmp --type 13 --code 0 --payload '\x01\x02\x03' 10.0.0.1`}, "\n"),
57+
Short: "Perform ICMP scan",
58+
PreRunE: func(cmd *cobra.Command, args []string) (err error) {
59+
if len(args) != 1 {
60+
return errors.New("requires one ip subnet argument")
61+
}
62+
var icmpType uint64
63+
if len(cliICMPTypeFlag) > 0 {
64+
if icmpType, err = strconv.ParseUint(cliICMPTypeFlag, 10, 8); err != nil {
65+
return
66+
}
67+
cliICMPType = uint8(icmpType)
68+
}
69+
var icmpCode uint64
70+
if len(cliICMPCodeFlag) > 0 {
71+
if icmpCode, err = strconv.ParseUint(cliICMPCodeFlag, 10, 8); err != nil {
72+
return
73+
}
74+
cliICMPCode = uint8(icmpCode)
75+
}
76+
if len(cliICMPPayloadFlag) > 0 {
77+
if cliICMPPayload, err = parsePacketPayload(cliICMPPayloadFlag); err != nil {
78+
return
79+
}
80+
}
81+
return
82+
},
83+
RunE: func(cmd *cobra.Command, args []string) (err error) {
84+
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
85+
defer cancel()
86+
87+
var conf *scanConfig
88+
if conf, err = parseScanConfig(icmp.ScanType, args[0]); err != nil {
89+
return
90+
}
91+
92+
m := newICMPScanMethod(ctx, conf)
93+
94+
return startPacketScanEngine(ctx, newPacketScanConfig(
95+
withPacketScanMethod(m),
96+
withPacketBPFFilter(icmp.BPFFilter),
97+
withPacketEngineConfig(newEngineConfig(
98+
withLogger(conf.logger),
99+
withScanRange(conf.scanRange),
100+
)),
101+
))
102+
},
103+
}
104+
105+
func newICMPScanMethod(ctx context.Context, conf *scanConfig) *icmp.ScanMethod {
106+
reqgen := arp.NewCacheRequestGenerator(
107+
scan.NewIPRequestGenerator(scan.NewIPGenerator()), conf.gatewayIP, conf.cache)
108+
pktgen := scan.NewPacketMultiGenerator(icmp.NewPacketFiller(getICMPOptions()...), runtime.NumCPU())
109+
psrc := scan.NewPacketSource(reqgen, pktgen)
110+
results := scan.NewResultChan(ctx, 1000)
111+
return icmp.NewScanMethod(psrc, results)
112+
}
113+
114+
func getICMPOptions() (opts []icmp.PacketFillerOption) {
115+
if len(cliIPTTLFlag) > 0 {
116+
opts = append(opts, icmp.WithTTL(cliTTL))
117+
}
118+
if len(cliIPTotalLenFlag) > 0 {
119+
opts = append(opts, icmp.WithIPTotalLength(cliIPTotalLen))
120+
}
121+
if len(cliIPProtocolFlag) > 0 {
122+
opts = append(opts, icmp.WithIPProtocol(cliIPProtocol))
123+
}
124+
if len(cliIPFlagsFlag) > 0 {
125+
opts = append(opts, icmp.WithIPFlags(cliIPFlags))
126+
}
127+
if len(cliICMPTypeFlag) > 0 {
128+
opts = append(opts, icmp.WithType(cliICMPType))
129+
}
130+
if len(cliICMPCodeFlag) > 0 {
131+
opts = append(opts, icmp.WithCode(cliICMPCode))
132+
}
133+
if len(cliICMPPayloadFlag) > 0 {
134+
opts = append(opts, icmp.WithPayload(cliICMPPayload))
135+
}
136+
return
137+
}

command/root.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"sync"
1313
"time"
1414

15+
"github.com/google/gopacket/layers"
1516
"github.com/google/gopacket/routing"
1617
"github.com/spf13/cobra"
1718
"github.com/v-byte-cpu/sx/command/log"
@@ -59,6 +60,32 @@ var rootCmd = &cobra.Command{
5960
return
6061
}
6162
}
63+
var ttl uint64
64+
if len(cliIPTTLFlag) > 0 {
65+
if ttl, err = strconv.ParseUint(cliIPTTLFlag, 10, 8); err != nil {
66+
return
67+
}
68+
cliTTL = uint8(ttl)
69+
}
70+
var ipLen uint64
71+
if len(cliIPTotalLenFlag) > 0 {
72+
if ipLen, err = strconv.ParseUint(cliIPTotalLenFlag, 10, 16); err != nil {
73+
return
74+
}
75+
cliIPTotalLen = uint16(ipLen)
76+
}
77+
var ipProto uint64
78+
if len(cliIPProtocolFlag) > 0 {
79+
if ipProto, err = strconv.ParseUint(cliIPProtocolFlag, 10, 8); err != nil {
80+
return
81+
}
82+
cliIPProtocol = uint8(ipProto)
83+
}
84+
if len(cliIPFlagsFlag) > 0 {
85+
if cliIPFlags, err = parseIPFlags(cliIPFlagsFlag); err != nil {
86+
return
87+
}
88+
}
6289
return
6390
},
6491
}
@@ -74,6 +101,10 @@ var (
74101
cliARPCacheFileFlag string
75102
cliIPPortFileFlag string
76103
cliProtoFlag string
104+
cliIPTTLFlag string
105+
cliIPTotalLenFlag string
106+
cliIPProtocolFlag string
107+
cliIPFlagsFlag string
77108

78109
cliInterface *net.Interface
79110
cliSrcIP net.IP
@@ -83,6 +114,10 @@ var (
83114
cliRateCount int
84115
cliRateWindow time.Duration
85116
cliExitDelay = 300 * time.Millisecond
117+
cliIPTotalLen uint16
118+
cliIPProtocol uint8
119+
cliIPFlags uint8
120+
cliTTL uint8
86121
)
87122

88123
const (
@@ -96,6 +131,7 @@ var (
96131
errSrcInterface = errors.New("invalid source interface")
97132
errRateLimit = errors.New("invalid ratelimit")
98133
errStdin = errors.New("stdin is from a terminal")
134+
errIPFlags = errors.New("invalid ip flags")
99135
)
100136

101137
func init() {
@@ -273,6 +309,34 @@ func parseRateLimit(rateLimit string) (rateCount int, rateWindow time.Duration,
273309
return
274310
}
275311

312+
func parsePacketPayload(payload string) (result []byte, err error) {
313+
var unquoted string
314+
if unquoted, err = strconv.Unquote(`"` + payload + `"`); err != nil {
315+
return
316+
}
317+
return []byte(unquoted), nil
318+
}
319+
320+
func parseIPFlags(inputFlags string) (result uint8, err error) {
321+
if len(inputFlags) == 0 {
322+
return
323+
}
324+
flags := strings.Split(strings.ToLower(inputFlags), ",")
325+
for _, flag := range flags {
326+
switch flag {
327+
case "df":
328+
result |= uint8(layers.IPv4DontFragment)
329+
case "evil":
330+
result |= uint8(layers.IPv4EvilBit)
331+
case "mf":
332+
result |= uint8(layers.IPv4MoreFragments)
333+
default:
334+
return 0, errIPFlags
335+
}
336+
}
337+
return
338+
}
339+
276340
func getSubnetInterface(dstSubnet *net.IPNet) (iface *net.Interface, srcSubnet *net.IPNet, err error) {
277341
if cliInterface == nil {
278342
return ip.GetSubnetInterface(dstSubnet)

0 commit comments

Comments
 (0)