Skip to content

Commit

Permalink
File option for packet scans
Browse files Browse the repository at this point in the history
  • Loading branch information
v-byte-cpu committed Apr 13, 2021
1 parent 34c072b commit a7a4e73
Show file tree
Hide file tree
Showing 30 changed files with 500 additions and 242 deletions.
18 changes: 10 additions & 8 deletions command/arp.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/spf13/cobra"
"github.com/v-byte-cpu/sx/command/log"
"github.com/v-byte-cpu/sx/pkg/ip"
"github.com/v-byte-cpu/sx/pkg/scan"
"github.com/v-byte-cpu/sx/pkg/scan/arp"
)
Expand All @@ -21,6 +22,7 @@ var (
)

func init() {
addPacketScanOptions(arpCmd, withoutGatewayMAC())
arpCmd.Flags().StringVar(&cliARPLiveTimeoutFlag, "live", "", "enable live mode")
rootCmd.AddCommand(arpCmd)
}
Expand All @@ -29,21 +31,21 @@ var arpCmd = &cobra.Command{
Use: "arp [flags] subnet",
Example: strings.Join([]string{"arp 192.168.0.1/24", "arp 10.0.0.1"}, "\n"),
Short: "Perform ARP scan",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("requires one ip subnet argument")
}
return nil
},
PreRunE: func(cmd *cobra.Command, args []string) (err error) {
if len(cliARPLiveTimeoutFlag) > 0 {
cliARPLiveTimeout, err = time.ParseDuration(cliARPLiveTimeoutFlag)
if cliARPLiveTimeout, err = time.ParseDuration(cliARPLiveTimeoutFlag); err != nil {
return
}
}
if len(args) != 1 {
return errors.New("requires one ip subnet argument")
}
cliDstSubnet, err = ip.ParseIPNet(args[0])
return
},
RunE: func(cmd *cobra.Command, args []string) (err error) {
var r *scan.Range
if r, err = parseScanRange(args[0]); err != nil {
if r, err = getScanRange(cliDstSubnet); err != nil {
return err
}

Expand Down
9 changes: 1 addition & 8 deletions command/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (

"github.com/spf13/cobra"
"github.com/v-byte-cpu/sx/command/log"
"github.com/v-byte-cpu/sx/pkg/ip"
"github.com/v-byte-cpu/sx/pkg/scan"
"github.com/v-byte-cpu/sx/pkg/scan/docker"
)
Expand All @@ -36,13 +35,7 @@ var dockerCmd = &cobra.Command{
if cliProtoFlag != cliHTTPProtoFlag && cliProtoFlag != cliHTTPSProtoFlag {
return errors.New("invalid HTTP proto flag: http or https required")
}
if len(args) == 0 && len(cliIPPortFileFlag) == 0 {
return errors.New("requires one ip subnet argument or file with ip/port pairs")
}
if len(args) == 0 {
return
}
cliDstSubnet, err = ip.ParseIPNet(args[0])
cliDstSubnet, err = parseDstSubnet(args)
return
},
RunE: func(cmd *cobra.Command, args []string) (err error) {
Expand Down
9 changes: 1 addition & 8 deletions command/elastic.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (

"github.com/spf13/cobra"
"github.com/v-byte-cpu/sx/command/log"
"github.com/v-byte-cpu/sx/pkg/ip"
"github.com/v-byte-cpu/sx/pkg/scan"
"github.com/v-byte-cpu/sx/pkg/scan/elastic"
)
Expand All @@ -36,13 +35,7 @@ var elasticCmd = &cobra.Command{
if cliProtoFlag != cliHTTPProtoFlag && cliProtoFlag != cliHTTPSProtoFlag {
return errors.New("invalid HTTP proto flag: http or https required")
}
if len(args) == 0 && len(cliIPPortFileFlag) == 0 {
return errors.New("requires one ip subnet argument or file with ip/port pairs")
}
if len(args) == 0 {
return
}
cliDstSubnet, err = ip.ParseIPNet(args[0])
cliDstSubnet, err = parseDstSubnet(args)
return
},
RunE: func(cmd *cobra.Command, args []string) (err error) {
Expand Down
18 changes: 13 additions & 5 deletions command/icmp.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package command

import (
"context"
"errors"
"io"
"os"
"os/signal"
"runtime"
Expand All @@ -26,6 +26,8 @@ var (
)

func init() {
addPacketScanOptions(icmpCmd)
icmpCmd.Flags().StringVarP(&cliIPPortFileFlag, "file", "f", "", "set JSONL file with IPs to scan")
icmpCmd.Flags().StringVar(&cliIPTTLFlag, "ttl", "",
strings.Join([]string{"set IP TTL field of generated packet", "64 by default"}, "\n"))
icmpCmd.Flags().StringVar(&cliIPTotalLenFlag, "iplen", "",
Expand Down Expand Up @@ -56,8 +58,8 @@ var icmpCmd = &cobra.Command{
`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")
if cliDstSubnet, err = parseDstSubnet(args); err != nil {
return
}
var icmpType uint64
if len(cliICMPTypeFlag) > 0 {
Expand Down Expand Up @@ -85,7 +87,7 @@ var icmpCmd = &cobra.Command{
defer cancel()

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

Expand All @@ -103,8 +105,14 @@ var icmpCmd = &cobra.Command{
}

func newICMPScanMethod(ctx context.Context, conf *scanConfig) *icmp.ScanMethod {
ipgen := scan.NewIPGenerator()
if len(cliIPPortFileFlag) > 0 {
ipgen = scan.NewFileIPGenerator(func() (io.ReadCloser, error) {
return os.Open(cliIPPortFileFlag)
})
}
reqgen := arp.NewCacheRequestGenerator(
scan.NewIPRequestGenerator(scan.NewIPGenerator()), conf.gatewayIP, conf.cache)
scan.NewIPRequestGenerator(ipgen), conf.gatewayMAC, conf.cache)
pktgen := scan.NewPacketMultiGenerator(icmp.NewPacketFiller(getICMPOptions()...), runtime.NumCPU())
psrc := scan.NewPacketSource(reqgen, pktgen)
results := scan.NewResultChan(ctx, 1000)
Expand Down
142 changes: 94 additions & 48 deletions command/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"time"

"github.com/google/gopacket/layers"
"github.com/google/gopacket/routing"
"github.com/spf13/cobra"
"github.com/v-byte-cpu/sx/command/log"
"github.com/v-byte-cpu/sx/pkg/ip"
Expand Down Expand Up @@ -45,6 +44,11 @@ var rootCmd = &cobra.Command{
return
}
}
if len(cliGatewayMACFlag) > 0 {
if cliGatewayMAC, err = net.ParseMAC(cliGatewayMACFlag); err != nil {
return
}
}
if len(cliPortsFlag) > 0 {
if cliPortRanges, err = parsePortRanges(cliPortsFlag); err != nil {
return
Expand Down Expand Up @@ -95,6 +99,7 @@ var (
cliInterfaceFlag string
cliSrcIPFlag string
cliSrcMACFlag string
cliGatewayMACFlag string
cliPortsFlag string
cliRateLimitFlag string
cliExitDelayFlag string
Expand All @@ -109,6 +114,7 @@ var (
cliInterface *net.Interface
cliSrcIP net.IP
cliSrcMAC net.HardwareAddr
cliGatewayMAC net.HardwareAddr
cliPortRanges []*scan.PortRange
cliDstSubnet *net.IPNet
cliRateCount int
Expand Down Expand Up @@ -136,16 +142,38 @@ var (

func init() {
rootCmd.PersistentFlags().BoolVar(&cliJSONFlag, "json", false, "enable JSON output")
rootCmd.PersistentFlags().StringVarP(&cliInterfaceFlag, "iface", "i", "", "set interface to send/receive packets")
rootCmd.PersistentFlags().StringVar(&cliSrcIPFlag, "srcip", "", "set source IP address for generated packets")
rootCmd.PersistentFlags().StringVar(&cliSrcMACFlag, "srcmac", "", "set source MAC address for generated packets")
rootCmd.PersistentFlags().StringVarP(&cliRateLimitFlag, "rate", "r", "",
}

type cliPacketScanConfig struct {
gatewayMAC bool
}

type cliPacketScanOption func(c *cliPacketScanConfig)

func withoutGatewayMAC() cliPacketScanOption {
return func(c *cliPacketScanConfig) {
c.gatewayMAC = false
}
}

func addPacketScanOptions(cmd *cobra.Command, opts ...cliPacketScanOption) {
conf := &cliPacketScanConfig{gatewayMAC: true}
for _, o := range opts {
o(conf)
}
cmd.PersistentFlags().StringVarP(&cliInterfaceFlag, "iface", "i", "", "set interface to send/receive packets")
cmd.PersistentFlags().StringVar(&cliSrcIPFlag, "srcip", "", "set source IP address for generated packets")
cmd.PersistentFlags().StringVar(&cliSrcMACFlag, "srcmac", "", "set source MAC address for generated packets")
if conf.gatewayMAC {
cmd.PersistentFlags().StringVar(&cliGatewayMACFlag, "gwmac", "", "set gateway MAC address to send generated packets to")
}
cmd.PersistentFlags().StringVarP(&cliRateLimitFlag, "rate", "r", "",
strings.Join([]string{
"set rate limit for generated packets",
`format: "rateCount/rateWindow"`,
"where rateCount is a number of packets, rateWindow is the time interval",
"e.g. 1000/s -- 1000 packets per second", "500/7s -- 500 packets per 7 seconds\n"}, "\n"))
rootCmd.PersistentFlags().StringVar(&cliExitDelayFlag, "exit-delay", "",
cmd.PersistentFlags().StringVar(&cliExitDelayFlag, "exit-delay", "",
strings.Join([]string{
"set exit delay to wait for response packets",
"any expression accepted by time.ParseDuration is valid (300ms by default)"}, "\n"))
Expand All @@ -158,15 +186,15 @@ func Main() {
}

type scanConfig struct {
logger log.Logger
scanRange *scan.Range
cache *arp.Cache
gatewayIP net.IP
logger log.Logger
scanRange *scan.Range
cache *arp.Cache
gatewayMAC net.HardwareAddr
}

func parseScanConfig(scanName, subnet string) (c *scanConfig, err error) {
func parseScanConfig(scanName string, dstSubnet *net.IPNet) (c *scanConfig, err error) {
var r *scan.Range
if r, err = parseScanRange(subnet); err != nil {
if r, err = getScanRange(dstSubnet); err != nil {
return
}

Expand All @@ -180,19 +208,30 @@ func parseScanConfig(scanName, subnet string) (c *scanConfig, err error) {
return
}

var gatewayIP net.IP
if gatewayIP, err = getGatewayIP(r); err != nil {
var gatewayMAC net.HardwareAddr
if gatewayMAC, err = getGatewayMAC(r.Interface, cache); err != nil {
return
}

c = &scanConfig{
logger: logger,
scanRange: r,
cache: cache,
gatewayIP: gatewayIP,
logger: logger,
scanRange: r,
cache: cache,
gatewayMAC: gatewayMAC,
}
return
}

func parseDstSubnet(args []string) (ipnet *net.IPNet, err error) {
if len(args) == 0 && len(cliIPPortFileFlag) == 0 {
return nil, errors.New("requires one ip subnet argument or file with ip/port pairs")
}
if len(args) == 0 {
return
}
return ip.ParseIPNet(args[0])
}

func parseARPCache() (cache *arp.Cache, err error) {
var r io.Reader
if len(cliARPCacheFileFlag) > 0 {
Expand All @@ -219,20 +258,15 @@ func parseARPCache() (cache *arp.Cache, err error) {
return
}

func parseScanRange(subnet string) (*scan.Range, error) {
dstSubnet, err := ip.ParseIPNet(subnet)
func getScanRange(dstSubnet *net.IPNet) (*scan.Range, error) {
iface, srcIP, err := getInterface(dstSubnet)
if err != nil {
return nil, err
}
iface, srcSubnet, err := getSubnetInterface(dstSubnet)
if err != nil {
return nil, err
}
if iface == nil || srcSubnet == nil {
if iface == nil {
return nil, errSrcInterface
}

srcIP := srcSubnet.IP
if cliSrcIP != nil {
srcIP = cliSrcIP
}
Expand All @@ -252,7 +286,6 @@ func parseScanRange(subnet string) (*scan.Range, error) {
Interface: iface,
DstSubnet: dstSubnet,
Ports: cliPortRanges,
SrcSubnet: srcSubnet,
SrcIP: srcIP.To4(),
SrcMAC: srcMAC}, nil
}
Expand Down Expand Up @@ -337,14 +370,44 @@ func parseIPFlags(inputFlags string) (result uint8, err error) {
return
}

func getSubnetInterface(dstSubnet *net.IPNet) (iface *net.Interface, srcSubnet *net.IPNet, err error) {
func getLocalSubnetInterface(dstSubnet *net.IPNet) (iface *net.Interface, ifaceIP net.IP, err error) {
if cliInterface == nil {
return ip.GetSubnetInterface(dstSubnet)
return ip.GetLocalSubnetInterface(dstSubnet)
}
ifaceIP, err = ip.GetLocalSubnetInterfaceIP(cliInterface, dstSubnet)
return cliInterface, ifaceIP, err
}

func getInterface(dstSubnet *net.IPNet) (iface *net.Interface, ifaceIP net.IP, err error) {
if dstSubnet != nil {
// try to find directly connected interface
if iface, ifaceIP, err = getLocalSubnetInterface(dstSubnet); err != nil {
return
}
// found local interface
if iface != nil && ifaceIP != nil {
return
}
}
if srcSubnet, err = ip.GetSubnetInterfaceIP(cliInterface, dstSubnet); err != nil {
if cliInterface != nil {
// try to get first ip address
ifaceIP, err = ip.GetInterfaceIP(cliInterface)
return cliInterface, ifaceIP, err
}
// fallback to interface of default gateway
return ip.GetDefaultInterface()
}

func getGatewayMAC(iface *net.Interface, cache *arp.Cache) (mac net.HardwareAddr, err error) {
if cliGatewayMAC != nil {
return cliGatewayMAC, nil
}
var gatewayIP net.IP
if gatewayIP, err = ip.GetDefaultGatewayIP(iface); err != nil {
return
}
return iface, srcSubnet, nil
mac = cache.Get(gatewayIP.To4())
return
}

func getLogger(name string, w io.Writer) (logger log.Logger, err error) {
Expand All @@ -356,23 +419,6 @@ func getLogger(name string, w io.Writer) (logger log.Logger, err error) {
return
}

func getGatewayIP(r *scan.Range) (gatewayIP net.IP, err error) {
var router routing.Router
if router, err = routing.New(); err != nil {
return
}
if _, gatewayIP, _, err = router.RouteWithSrc(
r.Interface.HardwareAddr, r.SrcIP, r.DstSubnet.IP); err != nil {
return
}
// if local address then don't need gateway
if gatewayIP == nil || r.DstSubnet.Contains(gatewayIP) {
return nil, nil
}
gatewayIP = gatewayIP.To4()
return
}

func newIPPortGenerator() (reqgen scan.RequestGenerator) {
if len(cliIPPortFileFlag) == 0 {
return scan.NewIPPortGenerator(scan.NewIPGenerator(), scan.NewPortGenerator())
Expand Down
Loading

0 comments on commit a7a4e73

Please sign in to comment.