From 210a15d99066fe74ad6cb4377a49a4195ad93bdb Mon Sep 17 00:00:00 2001 From: Ambrose Chua Date: Thu, 18 Jun 2020 00:46:58 +0800 Subject: [PATCH] Rewrite to be more "Correct" --- README.md | 7 +- go.mod | 8 +++ go.sum | 8 +++ main.go | 190 ++++++++++++++++++++++++++++++++-------------------- scramble.go | 56 ++++++++++++++++ 5 files changed, 191 insertions(+), 78 deletions(-) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 scramble.go diff --git a/README.md b/README.md index 828dc5f..0868e89 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,16 @@ # scramble -A simple tool to perform XOR as a TCP proxy. +Rewritten, this is now a basic XOR SOCKS proxy. ## Usage ``` $ ./scramble -help Usage of ./scramble: - -connect string - forward to ip and port (default ":8080") -key int key to xor the data (default 170) -listen string listen on ip and port (default ":8081") ``` -## Use with a SOCKS proxy - -This tool may come really useful when trying to bypass filters that perform packet inspection. After starting a SOCKS proxy listening on the server, `scramble` can connect to the proxy and listen on an exposed port to provide "obscured" SOCKS proxying if `scramble` is also run on the client. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ae2069d --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/serverwentdown/scramble + +go 1.14 + +require ( + github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 + golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b723d98 --- /dev/null +++ b/go.sum @@ -0,0 +1,8 @@ +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/main.go b/main.go index 9506560..4177850 100644 --- a/main.go +++ b/main.go @@ -2,105 +2,151 @@ package main import ( "flag" + "fmt" "io" "log" "net" + + "github.com/armon/go-socks5" ) -var key int +var keyInt int var listen string var connect string func main() { - flag.IntVar(&key, "key", 170, "key to xor the data") - flag.StringVar(&listen, "listen", ":8081", "listen on ip and port") - flag.StringVar(&connect, "connect", ":8080", "forward to ip and port") + flag.IntVar(&keyInt, "key", 170, "key to xor the data") + flag.StringVar(&listen, "listen", ":8081", "listen on IP and port") + flag.StringVar(&connect, "connect", "socks", "forward to IP and port. 'socks' sets up a SOCKS5 proxy.") flag.Parse() - if key < 0 || key > 255 { + if keyInt < 0 || keyInt > 255 { flag.PrintDefaults() - log.Fatal("key is not one byte") + log.Fatal(fmt.Errorf("key is not one byte")) + } + key := byte(keyInt) + + var socks *socks5.Server + if connect == "socks" { + conf := &socks5.Config{} + var err error + socks, err = socks5.New(conf) + if err != nil { + log.Fatal(fmt.Errorf("unable to create socks server: %w", err)) + } } // check and parse address - conn, err := net.ResolveTCPAddr("tcp", connect) - if err != nil { + connAddr, err := net.ResolveTCPAddr("tcp", connect) + if socks == nil && err != nil { flag.PrintDefaults() - log.Fatal(err) + log.Fatal(fmt.Errorf("invalid connect address %s: %w", connect, err)) } // listen on address ln, err := net.Listen("tcp", listen) if err != nil { flag.PrintDefaults() - log.Fatal(err) + log.Fatal(fmt.Errorf("unable to listen on %s: %w", listen, err)) + } + + for { + c, err := ln.Accept() + if err != nil { + log.Fatal(fmt.Errorf("unable to accept connection: %w", err)) + } + + log.Printf("connection from %v", c.RemoteAddr()) + + scrambleConn := NewScrambleConn(c, key) + if socks != nil { + go socks.ServeConn(scrambleConn) + } else { + conn, err := net.DialTCP("tcp", nil, connAddr) + if err != nil { + c.Close() + log.Print(fmt.Errorf("unable to connect to %v: %w", connAddr, err)) + continue + } + + result := Pipe(conn, scrambleConn) + go func() { + pipeResult := <-result + log.Printf("in: %d %v", pipeResult.Ingress.N, pipeResult.Ingress.Error) + log.Printf("eg: %d %v", pipeResult.Egress.N, pipeResult.Egress.Error) + }() + } } log.Printf("listening on %v", ln.Addr()) - log.Printf("will connect to %v", conn) - - for i := 0; ; i++ { - // accept new connection - c, err := ln.Accept() - if err != nil { - log.Fatal(err) - } - - log.Printf("connection %v from %v", i, c.RemoteAddr()) - - cn, err := net.DialTCP("tcp", nil, conn) - if err != nil { - c.Close() - log.Print(err) - continue - } - - go pipe(c, cn, byte(key), i) - go pipe(cn, c, byte(key), i) - } } -func pipe(w io.WriteCloser, r io.ReadCloser, key byte, count int) { - n, err := copyBufferXor(w, r, key) - - r.Close() - w.Close() - - log.Printf("connection %v closed, %v bytes", count, n) - - opError, ok := err.(*net.OpError) - if err != nil && (!ok || opError.Op != "readfrom") { - log.Printf("warning! %v", err) - } +type CloseIndividual interface { + CloseRead() error + CloseWrite() error } -func copyBufferXor(dst io.Writer, src io.Reader, key byte) (written int64, err error) { - buf := make([]byte, 32*1024) - for { - nr, er := src.Read(buf) - for i := 0; i < nr; i++ { - buf[i] = buf[i] ^ key - } - if nr > 0 { - nw, ew := dst.Write(buf[0:nr]) - if nw > 0 { - written += int64(nw) - } - if ew != nil { - err = ew - break - } - if nr != nw { - err = io.ErrShortWrite - break - } - } - if er != nil { - if er != io.EOF { - err = er - } - break - } - } - return written, err +type PipeResult struct { + Ingress CopyResult + Egress CopyResult +} + +func Pipe(a, b io.ReadWriteCloser) chan PipeResult { + // Copy from b to a + ingressResult := Copy(a, b) + // Copy from a to b + egressResult := Copy(b, a) + + result := make(chan PipeResult) + go func() { + var in CopyResult + var eg CopyResult + select { + case in = <-ingressResult: + // b returned error + // TODO: Consider error handling + closeOneSide(a, b) + eg = <-egressResult + case eg = <-egressResult: + // a returned error + // TODO: Consider error handling + closeOneSide(b, a) + in = <-ingressResult + } + + result <- PipeResult{ + Ingress: in, + Egress: eg, + } + }() + return result +} + +func closeOneSide(a, b io.ReadWriteCloser) (aErr error, bErr error) { + if c, ok := a.(CloseIndividual); ok { + aErr = c.CloseWrite() + } else { + aErr = a.Close() + } + if c, ok := b.(CloseIndividual); ok { + bErr = c.CloseRead() + } else { + bErr = b.Close() + } + return +} + +type CopyResult struct { + N int64 + Error error +} + +func Copy(w io.Writer, r io.Reader) chan CopyResult { + result := make(chan CopyResult) + go func() { + // Do a copy + n, err := io.Copy(w, r) + result <- CopyResult{n, err} + }() + return result } diff --git a/scramble.go b/scramble.go new file mode 100644 index 0000000..5f20e1f --- /dev/null +++ b/scramble.go @@ -0,0 +1,56 @@ +package main + +import ( + "io" + "net" +) + +type ScrambleReadWriter struct { + ReadWriter io.ReadWriter + Key byte + buffer []byte +} + +func (s *ScrambleReadWriter) Read(p []byte) (n int, err error) { + n, err = s.ReadWriter.Read(p) + for i := 0; i < n; i++ { + p[i] = p[i] ^ s.Key + } + return n, err +} + +// Write takes buffer p, performs XOR in a copy of the buffer and calls write +// on the underlying ReadWriter. +func (s *ScrambleReadWriter) Write(p []byte) (n int, err error) { + if cap(s.buffer) < cap(p) { + s.buffer = make([]byte, 0, cap(p)) + } else { + s.buffer = s.buffer[:0] + } + for i := range p { + s.buffer = append(s.buffer, p[i]^s.Key) + } + return s.ReadWriter.Write(s.buffer) +} + +type ScrambleConn struct { + net.Conn + *ScrambleReadWriter +} + +func NewScrambleConn(c net.Conn, key byte) net.Conn { + return &ScrambleConn{ + c, + &ScrambleReadWriter{ + ReadWriter: c, + Key: key, + }, + } +} + +func (s *ScrambleConn) Read(b []byte) (n int, err error) { + return s.ScrambleReadWriter.Read(b) +} +func (s *ScrambleConn) Write(b []byte) (n int, err error) { + return s.ScrambleReadWriter.Write(b) +}