Skip to content

Commit

Permalink
feature: vpn support (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
v-byte-cpu committed Jul 10, 2021
1 parent e75f9ff commit 29ca59d
Show file tree
Hide file tree
Showing 19 changed files with 606 additions and 144 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,14 @@ cat arp.cache | sx tcp syn -p 22 192.168.0.171

`tcp` subcomand is just a shorthand for `tcp syn` subcommand unless `--flags` option is passed, see below.

### VPN interfaces

`sx` supports scanning with virtual network interfaces (wireguard, openvpn, etc.) and in this case it is **not** necessary to use the arp cache, since these interfaces require raw IP packets instead of Ethernet frames as input. For instance, scanning an IP address on a vpn network:

```
sx tcp 10.1.27.1 -p 80 --json
```

### TCP FIN scan

Most network scanners try to interpret results of the scan. For instance they say "this port is closed" instead of "I received a RST". Sometimes they are right. Sometimes not. It's easier for beginners, but when you know what you're doing, you keep on trying to deduce what really happened from the program's interpretation, especially for more advanced scan techniques.
Expand Down
3 changes: 3 additions & 0 deletions command/arp.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ func newARPCmd() *arpCmd {
if r, err = c.opts.getScanRange(dstSubnet); err != nil {
return err
}
if r.SrcMAC == nil {
return errSrcMAC
}
var logger log.Logger
if logger, err = c.opts.getLogger(); err != nil {
return err
Expand Down
54 changes: 23 additions & 31 deletions command/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,6 @@ func (o *packetScanCmdOpts) getScanRange(dstSubnet *net.IPNet) (*scan.Range, err
if o.srcMAC != nil {
srcMAC = o.srcMAC
}
if srcMAC == nil {
return nil, errSrcMAC
}

return &scan.Range{
Interface: iface,
Expand Down Expand Up @@ -178,6 +175,11 @@ type ipScanCmdOpts struct {
ipFile string
arpCacheFile string
gatewayMAC net.HardwareAddr
vpnMode bool

logger log.Logger
scanRange *scan.Range
cache *arp.Cache

rawGatewayMAC string
}
Expand All @@ -202,52 +204,42 @@ func (o *ipScanCmdOpts) parseRawOptions() (err error) {
return
}

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

func (o *ipScanCmdOpts) parseScanConfig(scanName string, args []string) (c *scanConfig, err error) {
if err = o.validateStdin(); err != nil {
return
}
func (o *ipScanCmdOpts) parseOptions(scanName string, args []string) (err error) {

dstSubnet, err := o.parseDstSubnet(args)
if err != nil {
return
}
var r *scan.Range
if r, err = o.getScanRange(dstSubnet); err != nil {
if o.scanRange, err = o.getScanRange(dstSubnet); err != nil {
return
}
if o.scanRange.SrcMAC == nil {
o.vpnMode = true
}

var logger log.Logger
if logger, err = o.getLogger(scanName, os.Stdout); err != nil {
if o.logger, err = o.getLogger(scanName, os.Stdout); err != nil {
return
}

var cache *arp.Cache
if cache, err = o.parseARPCache(); err != nil {
// disable arp cache parsing for vpn mode
if o.vpnMode {
return
}
if err = o.validateARPStdin(); err != nil {
return
}

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

c = &scanConfig{
logger: logger,
scanRange: r,
cache: cache,
gatewayMAC: gatewayMAC,
if o.gatewayMAC, err = o.getGatewayMAC(o.scanRange.Interface, o.cache); err != nil {
return
}
return
}

func (o *ipScanCmdOpts) validateStdin() (err error) {
func (o *ipScanCmdOpts) validateARPStdin() (err error) {
if o.isARPCacheFromStdin() && o.ipFile == "-" {
return errARPStdin
}
Expand Down Expand Up @@ -333,11 +325,11 @@ func (o *ipPortScanCmdOpts) parseRawOptions() (err error) {
return
}

func (o *ipPortScanCmdOpts) parseScanConfig(scanName string, args []string) (c *scanConfig, err error) {
if c, err = o.ipScanCmdOpts.parseScanConfig(scanName, args); err != nil {
func (o *ipPortScanCmdOpts) parseOptions(scanName string, args []string) (err error) {
if err = o.ipScanCmdOpts.parseOptions(scanName, args); err != nil {
return
}
c.scanRange.Ports = o.portRanges
o.scanRange.Ports = o.portRanges
return
}

Expand Down
4 changes: 2 additions & 2 deletions command/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ func TestIPScanCmdOptsIsARPCacheFromStdin(t *testing.T) {
}
}

func TestIPScanCmdOptsValidateStdin(t *testing.T) {
func TestIPScanCmdOptsValidateARPStdin(t *testing.T) {
t.Parallel()
tests := []struct {
name string
Expand Down Expand Up @@ -265,7 +265,7 @@ func TestIPScanCmdOptsValidateStdin(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.opts.validateStdin()
err := tt.opts.validateARPStdin()
if tt.shouldErr {
require.Error(t, err)
} else {
Expand Down
21 changes: 12 additions & 9 deletions command/icmp.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,21 @@ func newICMPCmd() *icmpCmd {
if err = c.opts.parseRawOptions(); err != nil {
return
}
var conf *scanConfig
if conf, err = c.opts.parseScanConfig(icmp.ScanType, args); err != nil {
if err = c.opts.parseOptions(icmp.ScanType, args); err != nil {
return
}

m := c.opts.newICMPScanMethod(ctx, conf)
m := c.opts.newICMPScanMethod(ctx)

return startPacketScanEngine(ctx, newPacketScanConfig(
withPacketScanMethod(m),
withPacketBPFFilter(icmp.BPFFilter),
withRateCount(c.opts.rateCount),
withRateWindow(c.opts.rateWindow),
withPacketVPNmode(c.opts.vpnMode),
withPacketEngineConfig(newEngineConfig(
withLogger(conf.logger),
withScanRange(conf.scanRange),
withLogger(c.opts.logger),
withScanRange(c.opts.scanRange),
withExitDelay(c.opts.exitDelay),
)),
))
Expand Down Expand Up @@ -112,7 +112,7 @@ func (o *icmpCmdOpts) parseRawOptions() (err error) {
return
}

func (o *icmpCmdOpts) newICMPScanMethod(ctx context.Context, conf *scanConfig) *icmp.ScanMethod {
func (o *icmpCmdOpts) newICMPScanMethod(ctx context.Context) *icmp.ScanMethod {
ipgen := scan.NewIPGenerator()
if len(o.ipFile) > 0 {
ipgen = scan.NewFileIPGenerator(func() (io.ReadCloser, error) {
Expand All @@ -123,11 +123,13 @@ func (o *icmpCmdOpts) newICMPScanMethod(ctx context.Context, conf *scanConfig) *
if o.excludeIPs != nil {
reqgen = scan.NewFilterIPRequestGenerator(reqgen, o.excludeIPs)
}
reqgen = arp.NewCacheRequestGenerator(reqgen, conf.gatewayMAC, conf.cache)
if o.cache != nil {
reqgen = arp.NewCacheRequestGenerator(reqgen, o.gatewayMAC, o.cache)
}
pktgen := scan.NewPacketMultiGenerator(icmp.NewPacketFiller(o.getICMPOptions()...), runtime.NumCPU())
psrc := scan.NewPacketSource(reqgen, pktgen)
results := scan.NewResultChan(ctx, 1000)
return icmp.NewScanMethod(psrc, results)
return icmp.NewScanMethod(psrc, results, o.vpnMode)
}

func (o *icmpCmdOpts) getICMPOptions() (opts []icmp.PacketFillerOption) {
Expand All @@ -137,7 +139,8 @@ func (o *icmpCmdOpts) getICMPOptions() (opts []icmp.PacketFillerOption) {
icmp.WithIPFlags(o.ipFlags),
icmp.WithIPTotalLength(o.ipTotalLen),
icmp.WithType(o.icmpType),
icmp.WithCode(o.icmpCode))
icmp.WithCode(o.icmpCode),
icmp.WithVPNmode(o.vpnMode))

if len(o.icmpPayload) > 0 {
opts = append(opts, icmp.WithPayload(o.icmpPayload))
Expand Down
9 changes: 8 additions & 1 deletion command/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ type packetScanConfig struct {
bpfFilter bpfFilterFunc
rateCount int
rateWindow time.Duration
vpnMode bool
}

type packetScanConfigOption func(c *packetScanConfig)
Expand Down Expand Up @@ -128,6 +129,12 @@ func withRateWindow(rateWindow time.Duration) packetScanConfigOption {
}
}

func withPacketVPNmode(vpnMode bool) packetScanConfigOption {
return func(c *packetScanConfig) {
c.vpnMode = vpnMode
}
}

func newPacketScanConfig(opts ...packetScanConfigOption) *packetScanConfig {
c := &packetScanConfig{}
for _, o := range opts {
Expand All @@ -140,7 +147,7 @@ func startPacketScanEngine(ctx context.Context, conf *packetScanConfig) error {
r := conf.scanRange

// setup network interface to read/write packets
ps, err := afpacket.NewPacketSource(r.Interface.Name)
ps, err := afpacket.NewPacketSource(r.Interface.Name, conf.vpnMode)
if err != nil {
return err
}
Expand Down
37 changes: 21 additions & 16 deletions command/tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ func newTCPFlagsCmd() *tcpFlagsCmd {
}

scanName := tcp.FlagsScanType
var conf *scanConfig
if conf, err = c.opts.parseScanConfig(scanName, args); err != nil {
if err = c.opts.parseOptions(scanName, args); err != nil {
return
}

Expand All @@ -62,9 +61,9 @@ func newTCPFlagsCmd() *tcpFlagsCmd {
opts = append(opts, tcpPacketFlagOptions[flag])
}

m := c.opts.newTCPScanMethod(ctx, conf,
m := c.opts.newTCPScanMethod(ctx,
withTCPScanName(scanName),
withTCPPacketFiller(tcp.NewPacketFiller(opts...)),
withTCPPacketFillerOptions(opts...),
withTCPPacketFilterFunc(tcp.TrueFilter),
withTCPPacketFlags(tcp.AllFlags),
)
Expand All @@ -74,9 +73,10 @@ func newTCPFlagsCmd() *tcpFlagsCmd {
withPacketBPFFilter(tcp.BPFFilter),
withRateCount(c.opts.rateCount),
withRateWindow(c.opts.rateWindow),
withPacketVPNmode(c.opts.vpnMode),
withPacketEngineConfig(newEngineConfig(
withLogger(conf.logger),
withScanRange(conf.scanRange),
withLogger(c.opts.logger),
withScanRange(c.opts.scanRange),
withExitDelay(c.opts.exitDelay),
)),
))
Expand Down Expand Up @@ -146,26 +146,31 @@ type tcpCmdOpts struct {
ipPortScanCmdOpts
}

func (o *tcpCmdOpts) newTCPScanMethod(ctx context.Context, conf *scanConfig, opts ...tcpScanConfigOption) *tcp.ScanMethod {
func (o *tcpCmdOpts) newTCPScanMethod(ctx context.Context, opts ...tcpScanConfigOption) *tcp.ScanMethod {
c := &tcpScanConfig{}
for _, opt := range opts {
opt(c)
}
reqgen := arp.NewCacheRequestGenerator(o.newIPPortGenerator(), conf.gatewayMAC, conf.cache)
pktgen := scan.NewPacketMultiGenerator(c.packetFiller, runtime.NumCPU())
reqgen := o.newIPPortGenerator()
if o.cache != nil {
reqgen = arp.NewCacheRequestGenerator(reqgen, o.gatewayMAC, o.cache)
}
c.packetFillerOpts = append(c.packetFillerOpts, tcp.WithFillerVPNmode(o.vpnMode))
pktgen := scan.NewPacketMultiGenerator(tcp.NewPacketFiller(c.packetFillerOpts...), runtime.NumCPU())
psrc := scan.NewPacketSource(reqgen, pktgen)
results := scan.NewResultChan(ctx, 1000)
return tcp.NewScanMethod(
c.scanName, psrc, results,
tcp.WithPacketFilterFunc(c.packetFilter),
tcp.WithPacketFlagsFunc(c.packetFlags))
tcp.WithPacketFlagsFunc(c.packetFlags),
tcp.WithScanVPNmode(o.vpnMode))
}

type tcpScanConfig struct {
scanName string
packetFiller scan.PacketFiller
packetFilter tcp.PacketFilterFunc
packetFlags tcp.PacketFlagsFunc
scanName string
packetFillerOpts []tcp.PacketFillerOption
packetFilter tcp.PacketFilterFunc
packetFlags tcp.PacketFlagsFunc
}

type tcpScanConfigOption func(c *tcpScanConfig)
Expand All @@ -176,9 +181,9 @@ func withTCPScanName(scanName string) tcpScanConfigOption {
}
}

func withTCPPacketFiller(filler scan.PacketFiller) tcpScanConfigOption {
func withTCPPacketFillerOptions(opts ...tcp.PacketFillerOption) tcpScanConfigOption {
return func(c *tcpScanConfig) {
c.packetFiller = filler
c.packetFillerOpts = opts
}
}

Expand Down
12 changes: 6 additions & 6 deletions command/tcp_fin.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,13 @@ func newTCPFINCmd() *tcpFINCmd {
}

scanName := tcp.FINScanType
var conf *scanConfig
if conf, err = c.opts.parseScanConfig(scanName, args); err != nil {
if err = c.opts.parseOptions(scanName, args); err != nil {
return
}

m := c.opts.newTCPScanMethod(ctx, conf,
m := c.opts.newTCPScanMethod(ctx,
withTCPScanName(scanName),
withTCPPacketFiller(tcp.NewPacketFiller(tcp.WithFIN())),
withTCPPacketFillerOptions(tcp.WithFIN()),
withTCPPacketFilterFunc(tcp.TrueFilter),
withTCPPacketFlags(tcp.AllFlags),
)
Expand All @@ -43,9 +42,10 @@ func newTCPFINCmd() *tcpFINCmd {
withPacketBPFFilter(tcp.BPFFilter),
withRateCount(c.opts.rateCount),
withRateWindow(c.opts.rateWindow),
withPacketVPNmode(c.opts.vpnMode),
withPacketEngineConfig(newEngineConfig(
withLogger(conf.logger),
withScanRange(conf.scanRange),
withLogger(c.opts.logger),
withScanRange(c.opts.scanRange),
withExitDelay(c.opts.exitDelay),
)),
))
Expand Down
12 changes: 6 additions & 6 deletions command/tcp_null.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,13 @@ func newTCPNULLCmd() *tcpNULLCmd {
}

scanName := tcp.NULLScanType
var conf *scanConfig
if conf, err = c.opts.parseScanConfig(scanName, args); err != nil {
if err = c.opts.parseOptions(scanName, args); err != nil {
return
}

m := c.opts.newTCPScanMethod(ctx, conf,
m := c.opts.newTCPScanMethod(ctx,
withTCPScanName(scanName),
withTCPPacketFiller(tcp.NewPacketFiller()),
withTCPPacketFillerOptions(),
withTCPPacketFilterFunc(tcp.TrueFilter),
withTCPPacketFlags(tcp.AllFlags),
)
Expand All @@ -43,9 +42,10 @@ func newTCPNULLCmd() *tcpNULLCmd {
withPacketBPFFilter(tcp.BPFFilter),
withRateCount(c.opts.rateCount),
withRateWindow(c.opts.rateWindow),
withPacketVPNmode(c.opts.vpnMode),
withPacketEngineConfig(newEngineConfig(
withLogger(conf.logger),
withScanRange(conf.scanRange),
withLogger(c.opts.logger),
withScanRange(c.opts.scanRange),
withExitDelay(c.opts.exitDelay),
)),
))
Expand Down
Loading

0 comments on commit 29ca59d

Please sign in to comment.