Skip to content

Commit

Permalink
SOCKS scan
Browse files Browse the repository at this point in the history
  • Loading branch information
v-byte-cpu committed Apr 4, 2021
1 parent b2f8fa8 commit 189d984
Show file tree
Hide file tree
Showing 18 changed files with 1,357 additions and 181 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The goal of this project is to create the fastest network scanner with clean and
* **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
* **UDP scan**: Scan UDP ports and get full ICMP replies to detect open ports or firewall rules
* **SOCKS5 scan**: Detect live SOCKS5 proxies by scanning ip range or list of ip/port pairs from a file
* **JSON output support**: sx is designed specifically for convenient automatic processing of results

## Build from source
Expand Down
17 changes: 17 additions & 0 deletions command/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,13 @@ var (
cliRateLimitFlag string
cliExitDelayFlag string
cliARPCacheFileFlag string
cliIPPortFileFlag string

cliInterface *net.Interface
cliSrcIP net.IP
cliSrcMAC net.HardwareAddr
cliPortRanges []*scan.PortRange
cliDstSubnet *net.IPNet
cliRateCount int
cliRateWindow time.Duration
cliExitDelay = 300 * time.Millisecond
Expand Down Expand Up @@ -301,6 +303,21 @@ func getGatewayIP(r *scan.Range) (gatewayIP net.IP, err error) {
return
}

func newIPPortGenerator() (reqgen scan.RequestGenerator) {
if len(cliIPPortFileFlag) == 0 {
return scan.NewIPPortGenerator(scan.NewIPGenerator(), scan.NewPortGenerator())
}
if len(cliPortRanges) == 0 {
return scan.NewFileIPPortGenerator(func() (io.ReadCloser, error) {
return os.Open(cliIPPortFileFlag)
})
}
ipgen := scan.NewFileIPGenerator(func() (io.ReadCloser, error) {
return os.Open(cliIPPortFileFlag)
})
return scan.NewIPPortGenerator(ipgen, scan.NewPortGenerator())
}

type bpfFilterFunc func(r *scan.Range) (filter string, maxPacketLength int)

type engineConfig struct {
Expand Down
70 changes: 70 additions & 0 deletions command/socks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package command

import (
"context"
"errors"
"os"
"os/signal"
"strings"
"time"

"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/socks5"
)

func init() {
socksCmd.Flags().StringVarP(&cliPortsFlag, "ports", "p", "", "set ports to scan")
socksCmd.Flags().StringVarP(&cliIPPortFileFlag, "file", "f", "", "set JSONL file with ip/port pairs to scan")
rootCmd.AddCommand(socksCmd)
}

var socksCmd = &cobra.Command{
Use: "socks [flags] subnet",
Example: strings.Join([]string{
"socks -p 1080 192.168.0.1/24", "socks -p 1080-4567 10.0.0.1",
"socks -f ip_ports_file.jsonl", "socks -p 1080-4567 -f ips_file.jsonl"}, "\n"),
Short: "Perform SOCKS5 scan",
// Long: "Perform SOCKS scan. SOCKS5 scan is used by default unless --version option is specified",
PreRunE: func(cmd *cobra.Command, args []string) (err error) {
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])
return
},
RunE: func(cmd *cobra.Command, args []string) (err error) {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()

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

engine := newSOCKSScanEngine(ctx)
return startScanEngine(ctx, engine,
newEngineConfig(
withLogger(logger),
withScanRange(&scan.Range{
DstSubnet: cliDstSubnet,
Ports: cliPortRanges,
}),
))
},
}

func newSOCKSScanEngine(ctx context.Context) scan.EngineResulter {
// TODO custom dialTimeout, dataTimeout
scanner := socks5.NewScanner(
socks5.WithDialTimeout(2*time.Second),
socks5.WithDataTimeout(2*time.Second))
results := scan.NewResultChan(ctx, 1000)
// TODO custom workerCount
return scan.NewScanEngine(newIPPortGenerator(), scanner, results, scan.WithScanWorkerCount(50))
}
2 changes: 1 addition & 1 deletion command/tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func newTCPScanMethod(ctx context.Context, conf *scanConfig, opts ...tcpScanConf
portgen := scan.NewPortGenerator()
ipgen := scan.NewIPGenerator()
reqgen := arp.NewCacheRequestGenerator(
scan.NewIPPortRequestGenerator(ipgen, portgen), conf.gatewayIP, conf.cache)
scan.NewIPPortGenerator(ipgen, portgen), conf.gatewayIP, conf.cache)
pktgen := scan.NewPacketMultiGenerator(c.packetFiller, runtime.NumCPU())
psrc := scan.NewPacketSource(reqgen, pktgen)
results := scan.NewResultChan(ctx, 1000)
Expand Down
6 changes: 5 additions & 1 deletion command/udp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package command
import (
"context"
"errors"
golog "log"
"os"
"os/signal"
"runtime"
Expand All @@ -17,6 +18,9 @@ import (

func init() {
udpCmd.Flags().StringVarP(&cliPortsFlag, "ports", "p", "", "set ports to scan")
if err := udpCmd.MarkFlagRequired("ports"); err != nil {
golog.Fatalln(err)
}
udpCmd.Flags().StringVarP(&cliARPCacheFileFlag, "arp-cache", "a", "",
strings.Join([]string{"set ARP cache file", "reads from stdin by default"}, "\n"))
rootCmd.AddCommand(udpCmd)
Expand Down Expand Up @@ -58,7 +62,7 @@ func newUDPScanMethod(ctx context.Context, conf *scanConfig) *udp.ScanMethod {
portgen := scan.NewPortGenerator()
ipgen := scan.NewIPGenerator()
reqgen := arp.NewCacheRequestGenerator(
scan.NewIPPortRequestGenerator(ipgen, portgen), conf.gatewayIP, conf.cache)
scan.NewIPPortGenerator(ipgen, portgen), conf.gatewayIP, conf.cache)
pktgen := scan.NewPacketMultiGenerator(udp.NewPacketFiller(), runtime.NumCPU())
psrc := scan.NewPacketSource(reqgen, pktgen)
results := scan.NewResultChan(ctx, 1000)
Expand Down
106 changes: 100 additions & 6 deletions pkg/scan/engine.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:generate mockgen -package scan -destination=mock_engine_test.go . PacketSource
//go:generate mockgen -package scan -destination=mock_engine_test.go . PacketSource,Scanner

package scan

Expand Down Expand Up @@ -103,11 +103,7 @@ func mergeErrChan(ctx context.Context, channels ...<-chan error) <-chan error {
if !ok {
return
}
select {
case <-ctx.Done():
return
case out <- e:
}
writeError(ctx, out, e)
}
}
}
Expand All @@ -133,3 +129,101 @@ func SetupPacketEngine(rw packet.ReadWriter, m PacketMethod) EngineResulter {
engine := NewPacketEngine(m, sender, receiver)
return NewEngineResulter(engine, m)
}

type Scanner interface {
Scan(ctx context.Context, r *Request) (Result, error)
}

type GenericEngine struct {
reqgen RequestGenerator
scanner Scanner
results ResultChan
workerCount int
}

// Assert that GenericEngine conforms to the scan.EngineResulter interface
var _ EngineResulter = (*GenericEngine)(nil)

type GenericEngineOption func(s *GenericEngine)

func WithScanWorkerCount(workerCount int) GenericEngineOption {
return func(s *GenericEngine) {
s.workerCount = workerCount
}
}

func NewScanEngine(reqgen RequestGenerator,
scanner Scanner, results ResultChan, opts ...GenericEngineOption) *GenericEngine {
s := &GenericEngine{
reqgen: reqgen,
scanner: scanner,
results: results,
workerCount: 50,
}
for _, o := range opts {
o(s)
}
return s
}

func (e *GenericEngine) Results() <-chan Result {
return e.results.Chan()
}

func (e *GenericEngine) Start(ctx context.Context, r *Range) (<-chan interface{}, <-chan error) {
done := make(chan interface{})
errc := make(chan error, 100)
requests, err := e.reqgen.GenerateRequests(ctx, r)
if err != nil {
errc <- err
close(errc)
close(done)
return done, errc
}
go func() {
defer close(done)
defer close(errc)
var wg sync.WaitGroup
for i := 1; i <= e.workerCount; i++ {
wg.Add(1)
go e.worker(ctx, &wg, requests, errc)
}
wg.Wait()
}()
return done, errc
}

func (e *GenericEngine) worker(ctx context.Context, wg *sync.WaitGroup,
requests <-chan *Request, errc chan<- error) {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case r, ok := <-requests:
if !ok {
return
}
if r.Err != nil {
writeError(ctx, errc, r.Err)
continue
}
result, err := e.scanner.Scan(ctx, r)
if err != nil {
writeError(ctx, errc, err)
continue
}
if result != nil {
e.results.Put(result)
}
}
}
}

func writeError(ctx context.Context, out chan<- error, err error) {
select {
case <-ctx.Done():
return
case out <- err:
}
}
Loading

0 comments on commit 189d984

Please sign in to comment.