Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: vpn support #94

Merged
merged 1 commit into from
Jul 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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