Skip to content

Commit

Permalink
ICMP scan
Browse files Browse the repository at this point in the history
  • Loading branch information
v-byte-cpu committed Apr 11, 2021
1 parent db28814 commit cf5eab5
Show file tree
Hide file tree
Showing 10 changed files with 1,117 additions and 85 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The goal of this project is to create the fastest network scanner with clean and
## Features

* **ARP scan**: Scan your local networks to detect live devices
* **ICMP scan**: Use advanced ICMP scanning techniques to detect live hosts and firewall rules
* **TCP SYN scan**: Traditional half-open scan to find open TCP ports
* **TCP FIN / NULL / Xmas scans**: Scan techniques to bypass some firewall rules
* **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
Expand Down Expand Up @@ -388,6 +389,17 @@ In this case only ip addresses will be taken from the file and the **port** fiel
./sx help
```

## Reference

* **Network Security Assessment: Know Your Network 1st Edition** by Chris McNab
* **ICMP Usage in Scanning - The Complete Know-How** by Ofir Arkin
* [Transmission Control Protocol ( rfc793 )](https://tools.ietf.org/rfc/rfc793.txt)
* [User Datagram Protocol ( rfc768 )](https://tools.ietf.org/rfc/rfc768.txt)
* [Requirements for Internet Hosts -- Communication Layers ( rfc1122 )](https://tools.ietf.org/rfc/rfc1122.txt)
* [SOCKS Protocol Version 5 ( rfc1928 )](https://tools.ietf.org/rfc/rfc1928.txt)
* [Internet Control Message Protocol ( rfc792 )](https://tools.ietf.org/rfc/rfc792.txt)


## License

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.
137 changes: 137 additions & 0 deletions command/icmp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package command

import (
"context"
"errors"
"os"
"os/signal"
"runtime"
"strconv"
"strings"

"github.com/spf13/cobra"
"github.com/v-byte-cpu/sx/pkg/scan"
"github.com/v-byte-cpu/sx/pkg/scan/arp"
"github.com/v-byte-cpu/sx/pkg/scan/icmp"
)

var (
cliICMPTypeFlag string
cliICMPCodeFlag string
cliICMPPayloadFlag string

cliICMPType uint8
cliICMPCode uint8
cliICMPPayload []byte
)

func init() {
icmpCmd.Flags().StringVar(&cliIPTTLFlag, "ttl", "",
strings.Join([]string{"set IP TTL field of generated packet", "64 by default"}, "\n"))
icmpCmd.Flags().StringVar(&cliIPTotalLenFlag, "iplen", "",
strings.Join([]string{"set IP Total Length field of generated packet", "calculated by default"}, "\n"))
icmpCmd.Flags().StringVar(&cliIPProtocolFlag, "ipproto", "",
strings.Join([]string{"set IP Protocol field of generated packet", "1 (ICMP) by default"}, "\n"))
icmpCmd.Flags().StringVar(&cliIPFlagsFlag, "ipflags", "",
strings.Join([]string{"set IP Flags field of generated packet", "DF by default"}, "\n"))

icmpCmd.Flags().StringVarP(&cliICMPTypeFlag, "type", "t", "",
strings.Join([]string{"set ICMP type of generated packet", "ICMP Echo (Type 8) by default"}, "\n"))
icmpCmd.Flags().StringVarP(&cliICMPCodeFlag, "code", "c", "",
strings.Join([]string{"set ICMP code of generated packet", "0 by default"}, "\n"))
icmpCmd.Flags().StringVarP(&cliICMPPayloadFlag, "payload", "p", "",
strings.Join([]string{"set byte payload of generated packet", "48 random bytes by default"}, "\n"))

icmpCmd.Flags().StringVarP(&cliARPCacheFileFlag, "arp-cache", "a", "",
strings.Join([]string{"set ARP cache file", "reads from stdin by default"}, "\n"))
rootCmd.AddCommand(icmpCmd)
}

var icmpCmd = &cobra.Command{
Use: "icmp [flags] subnet",
Example: strings.Join([]string{
"icmp 192.168.0.1/24",
"icmp --ttl 37 192.168.0.1/24",
"icmp --ipproto 157 192.168.0.1/24",
`icmp --type 13 --code 0 --payload '\x01\x02\x03' 10.0.0.1`}, "\n"),
Short: "Perform ICMP scan",
PreRunE: func(cmd *cobra.Command, args []string) (err error) {
if len(args) != 1 {
return errors.New("requires one ip subnet argument")
}
var icmpType uint64
if len(cliICMPTypeFlag) > 0 {
if icmpType, err = strconv.ParseUint(cliICMPTypeFlag, 10, 8); err != nil {
return
}
cliICMPType = uint8(icmpType)
}
var icmpCode uint64
if len(cliICMPCodeFlag) > 0 {
if icmpCode, err = strconv.ParseUint(cliICMPCodeFlag, 10, 8); err != nil {
return
}
cliICMPCode = uint8(icmpCode)
}
if len(cliICMPPayloadFlag) > 0 {
if cliICMPPayload, err = parsePacketPayload(cliICMPPayloadFlag); err != nil {
return
}
}
return
},
RunE: func(cmd *cobra.Command, args []string) (err error) {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()

var conf *scanConfig
if conf, err = parseScanConfig(icmp.ScanType, args[0]); err != nil {
return
}

m := newICMPScanMethod(ctx, conf)

return startPacketScanEngine(ctx, newPacketScanConfig(
withPacketScanMethod(m),
withPacketBPFFilter(icmp.BPFFilter),
withPacketEngineConfig(newEngineConfig(
withLogger(conf.logger),
withScanRange(conf.scanRange),
)),
))
},
}

func newICMPScanMethod(ctx context.Context, conf *scanConfig) *icmp.ScanMethod {
reqgen := arp.NewCacheRequestGenerator(
scan.NewIPRequestGenerator(scan.NewIPGenerator()), conf.gatewayIP, conf.cache)
pktgen := scan.NewPacketMultiGenerator(icmp.NewPacketFiller(getICMPOptions()...), runtime.NumCPU())
psrc := scan.NewPacketSource(reqgen, pktgen)
results := scan.NewResultChan(ctx, 1000)
return icmp.NewScanMethod(psrc, results)
}

func getICMPOptions() (opts []icmp.PacketFillerOption) {
if len(cliIPTTLFlag) > 0 {
opts = append(opts, icmp.WithTTL(cliTTL))
}
if len(cliIPTotalLenFlag) > 0 {
opts = append(opts, icmp.WithIPTotalLength(cliIPTotalLen))
}
if len(cliIPProtocolFlag) > 0 {
opts = append(opts, icmp.WithIPProtocol(cliIPProtocol))
}
if len(cliIPFlagsFlag) > 0 {
opts = append(opts, icmp.WithIPFlags(cliIPFlags))
}
if len(cliICMPTypeFlag) > 0 {
opts = append(opts, icmp.WithType(cliICMPType))
}
if len(cliICMPCodeFlag) > 0 {
opts = append(opts, icmp.WithCode(cliICMPCode))
}
if len(cliICMPPayloadFlag) > 0 {
opts = append(opts, icmp.WithPayload(cliICMPPayload))
}
return
}
64 changes: 64 additions & 0 deletions command/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"sync"
"time"

"github.com/google/gopacket/layers"
"github.com/google/gopacket/routing"
"github.com/spf13/cobra"
"github.com/v-byte-cpu/sx/command/log"
Expand Down Expand Up @@ -59,6 +60,32 @@ var rootCmd = &cobra.Command{
return
}
}
var ttl uint64
if len(cliIPTTLFlag) > 0 {
if ttl, err = strconv.ParseUint(cliIPTTLFlag, 10, 8); err != nil {
return
}
cliTTL = uint8(ttl)
}
var ipLen uint64
if len(cliIPTotalLenFlag) > 0 {
if ipLen, err = strconv.ParseUint(cliIPTotalLenFlag, 10, 16); err != nil {
return
}
cliIPTotalLen = uint16(ipLen)
}
var ipProto uint64
if len(cliIPProtocolFlag) > 0 {
if ipProto, err = strconv.ParseUint(cliIPProtocolFlag, 10, 8); err != nil {
return
}
cliIPProtocol = uint8(ipProto)
}
if len(cliIPFlagsFlag) > 0 {
if cliIPFlags, err = parseIPFlags(cliIPFlagsFlag); err != nil {
return
}
}
return
},
}
Expand All @@ -74,6 +101,10 @@ var (
cliARPCacheFileFlag string
cliIPPortFileFlag string
cliProtoFlag string
cliIPTTLFlag string
cliIPTotalLenFlag string
cliIPProtocolFlag string
cliIPFlagsFlag string

cliInterface *net.Interface
cliSrcIP net.IP
Expand All @@ -83,6 +114,10 @@ var (
cliRateCount int
cliRateWindow time.Duration
cliExitDelay = 300 * time.Millisecond
cliIPTotalLen uint16
cliIPProtocol uint8
cliIPFlags uint8
cliTTL uint8
)

const (
Expand All @@ -96,6 +131,7 @@ var (
errSrcInterface = errors.New("invalid source interface")
errRateLimit = errors.New("invalid ratelimit")
errStdin = errors.New("stdin is from a terminal")
errIPFlags = errors.New("invalid ip flags")
)

func init() {
Expand Down Expand Up @@ -273,6 +309,34 @@ func parseRateLimit(rateLimit string) (rateCount int, rateWindow time.Duration,
return
}

func parsePacketPayload(payload string) (result []byte, err error) {
var unquoted string
if unquoted, err = strconv.Unquote(`"` + payload + `"`); err != nil {
return
}
return []byte(unquoted), nil
}

func parseIPFlags(inputFlags string) (result uint8, err error) {
if len(inputFlags) == 0 {
return
}
flags := strings.Split(strings.ToLower(inputFlags), ",")
for _, flag := range flags {
switch flag {
case "df":
result |= uint8(layers.IPv4DontFragment)
case "evil":
result |= uint8(layers.IPv4EvilBit)
case "mf":
result |= uint8(layers.IPv4MoreFragments)
default:
return 0, errIPFlags
}
}
return
}

func getSubnetInterface(dstSubnet *net.IPNet) (iface *net.Interface, srcSubnet *net.IPNet, err error) {
if cliInterface == nil {
return ip.GetSubnetInterface(dstSubnet)
Expand Down
Loading

0 comments on commit cf5eab5

Please sign in to comment.