diff --git a/cmd/spoof-dpi/main.go b/cmd/spoof-dpi/main.go index 7df4c08..f49cc19 100644 --- a/cmd/spoof-dpi/main.go +++ b/cmd/spoof-dpi/main.go @@ -15,7 +15,7 @@ func main() { util.ParseArgs() config := util.GetConfig() - p := proxy.New(config) + pxy := proxy.New(config) if *config.Debug { log.SetLevel(log.DebugLevel) } else { @@ -36,7 +36,7 @@ func main() { log.Fatal(err) } - go p.Start() + go pxy.Start() // Handle signals sigs := make(chan os.Signal, 1) diff --git a/net/conn.go b/net/conn.go index e152854..d258bdd 100644 --- a/net/conn.go +++ b/net/conn.go @@ -7,8 +7,6 @@ import ( "time" log "github.com/sirupsen/logrus" - "github.com/xvzc/SpoofDPI/dns" - "github.com/xvzc/SpoofDPI/packet" ) const BUF_SIZE = 1024 @@ -59,116 +57,6 @@ func (conn *Conn) ReadBytes() ([]byte, error) { return ret, nil } -func (lConn *Conn) HandleHttp(p *packet.HttpPacket, timeout int, resolver *dns.DnsResolver) { - p.Tidy() - - ip, err := resolver.Lookup(p.Domain()) - if err != nil { - log.Error("[HTTP] Error looking up for domain with ", p.Domain(), " ", err) - lConn.Write([]byte(p.Version() + " 502 Bad Gateway\r\n\r\n")) - return - } - - // Create connection to server - var port = "80" - if p.Port() != "" { - port = p.Port() - } - - rConn, err := DialTCP("tcp", ip, port) - if err != nil { - log.Debug("[HTTP] ", err) - return - } - - defer func() { - lConn.Close() - log.Debug("[HTTP] Closing client Connection.. ", lConn.RemoteAddr()) - - rConn.Close() - log.Debug("[HTTP] Closing server Connection.. ", p.Domain(), " ", rConn.LocalAddr()) - }() - - log.Debug("[HTTP] New connection to the server ", p.Domain(), " ", rConn.LocalAddr()) - - go rConn.Serve(lConn, "[HTTP]", lConn.RemoteAddr().String(), p.Domain(), timeout) - - _, err = rConn.Write(p.Raw()) - if err != nil { - log.Debug("[HTTP] Error sending request to ", p.Domain(), err) - return - } - - log.Debug("[HTTP] Sent a request to ", p.Domain()) - - lConn.Serve(rConn, "[HTTP]", lConn.RemoteAddr().String(), p.Domain(), timeout) - -} - -func (lConn *Conn) HandleHttps(p *packet.HttpPacket, timeout int, resolver *dns.DnsResolver) { - ip, err := resolver.Lookup(p.Domain()) - - if err != nil { - log.Error("[HTTPS] Error looking up for domain: ", p.Domain(), " ", err) - lConn.Write([]byte(p.Version() + " 502 Bad Gateway\r\n\r\n")) - return - } - - // Create a connection to the requested server - var port = "443" - if p.Port() != "" { - port = p.Port() - } - - rConn, err := DialTCP("tcp4", ip, port) - if err != nil { - log.Debug("[HTTPS] ", err) - return - } - - defer func() { - lConn.Close() - log.Debug("[HTTPS] Closing client Connection.. ", lConn.RemoteAddr()) - - rConn.Close() - log.Debug("[HTTPS] Closing server Connection.. ", p.Domain(), " ", rConn.LocalAddr()) - }() - - log.Debug("[HTTPS] New connection to the server ", p.Domain(), " ", rConn.LocalAddr()) - - _, err = lConn.Write([]byte(p.Version() + " 200 Connection Established\r\n\r\n")) - if err != nil { - log.Debug("[HTTPS] Error sending 200 Connection Established to the client", err) - return - } - - log.Debug("[HTTPS] Sent 200 Connection Estabalished to ", lConn.RemoteAddr()) - - // Read client hello - clientHello, err := lConn.ReadBytes() - if err != nil { - log.Debug("[HTTPS] Error reading client hello from the client", err) - return - } - - log.Debug("[HTTPS] Client sent hello ", len(clientHello), "bytes") - - // Generate a go routine that reads from the server - - pkt := packet.NewHttpsPacket(clientHello) - - chunks := pkt.SplitInChunks() - - go rConn.Serve(lConn, "[HTTPS]", rConn.RemoteAddr().String(), p.Domain(), timeout) - - if _, err := rConn.WriteChunks(chunks); err != nil { - log.Debug("[HTTPS] Error writing client hello to ", p.Domain(), err) - return - } - - lConn.Serve(rConn, "[HTTPS]", lConn.RemoteAddr().String(), p.Domain(), timeout) -} - func (from *Conn) Serve(to *Conn, proto string, fd string, td string, timeout int) { proto += " " diff --git a/packet/https.go b/packet/https.go index 6cf8c89..fc857b2 100644 --- a/packet/https.go +++ b/packet/https.go @@ -1,9 +1,5 @@ package packet -import ( - "github.com/xvzc/SpoofDPI/util" -) - type HttpsPacket struct { raw []byte } @@ -17,21 +13,3 @@ func NewHttpsPacket(raw []byte) HttpsPacket { func (p *HttpsPacket) Raw() []byte { return p.raw } - -func (p *HttpsPacket) SplitInChunks() [][]byte { - if len(p.Raw()) < 1 { - return [][]byte{p.Raw()} - } - config := util.GetConfig() - - // If the packet matches the pattern or the URLs, we don't split it - if config.PatternExists() { - if (config.PatternMatches(p.Raw())) { - return [][]byte{(p.Raw())[:1], (p.Raw())[1:]} - } - - return [][]byte{p.Raw()} - } - - return [][]byte{(p.Raw())[:1], (p.Raw())[1:]} -} diff --git a/proxy/http.go b/proxy/http.go new file mode 100644 index 0000000..922cd9e --- /dev/null +++ b/proxy/http.go @@ -0,0 +1,46 @@ +package proxy + +import ( + log "github.com/sirupsen/logrus" + "github.com/xvzc/SpoofDPI/net" + "github.com/xvzc/SpoofDPI/packet" +) + +func (pxy *Proxy) HandleHttp(lConn *net.Conn, pkt *packet.HttpPacket, ip string) { + pkt.Tidy() + + // Create connection to server + var port = "80" + if pkt.Port() != "" { + port = pkt.Port() + } + + rConn, err := net.DialTCP("tcp", ip, port) + if err != nil { + lConn.Close() + log.Debug("[HTTP] ", err) + return + } + + defer func() { + lConn.Close() + log.Debug("[HTTP] Closing client Connection.. ", lConn.RemoteAddr()) + + rConn.Close() + log.Debug("[HTTP] Closing server Connection.. ", pkt.Domain(), " ", rConn.LocalAddr()) + }() + + log.Debug("[HTTP] New connection to the server ", pkt.Domain(), " ", rConn.LocalAddr()) + + go rConn.Serve(lConn, "[HTTP]", lConn.RemoteAddr().String(), pkt.Domain(), pxy.timeout) + + _, err = rConn.Write(pkt.Raw()) + if err != nil { + log.Debug("[HTTP] Error sending request to ", pkt.Domain(), err) + return + } + + log.Debug("[HTTP] Sent a request to ", pkt.Domain()) + + lConn.Serve(rConn, "[HTTP]", lConn.RemoteAddr().String(), pkt.Domain(), pxy.timeout) +} diff --git a/proxy/https.go b/proxy/https.go new file mode 100644 index 0000000..fb5fa94 --- /dev/null +++ b/proxy/https.go @@ -0,0 +1,106 @@ +package proxy + +import ( + log "github.com/sirupsen/logrus" + "github.com/xvzc/SpoofDPI/net" + "github.com/xvzc/SpoofDPI/packet" +) + +func (pxy *Proxy) HandleHttps(lConn *net.Conn, initPkt *packet.HttpPacket, ip string) { + // Create a connection to the requested server + var port = "443" + if initPkt.Port() != "" { + port = initPkt.Port() + } + + rConn, err := net.DialTCP("tcp4", ip, port) + if err != nil { + lConn.Close() + log.Debug("[HTTPS] ", err) + return + } + + defer func() { + lConn.Close() + log.Debug("[HTTPS] Closing client Connection.. ", lConn.RemoteAddr()) + + rConn.Close() + log.Debug("[HTTPS] Closing server Connection.. ", initPkt.Domain(), " ", rConn.LocalAddr()) + }() + + log.Debug("[HTTPS] New connection to the server ", initPkt.Domain(), " ", rConn.LocalAddr()) + + _, err = lConn.Write([]byte(initPkt.Version() + " 200 Connection Established\r\n\r\n")) + if err != nil { + log.Debug("[HTTPS] Error sending 200 Connection Established to the client", err) + return + } + + log.Debug("[HTTPS] Sent 200 Connection Estabalished to ", lConn.RemoteAddr()) + + // Read client hello + clientHello, err := lConn.ReadBytes() + if err != nil { + log.Debug("[HTTPS] Error reading client hello from the client", err) + return + } + + log.Debug("[HTTPS] Client sent hello ", len(clientHello), "bytes") + + // Generate a go routine that reads from the server + + chPkt := packet.NewHttpsPacket(clientHello) + + go rConn.Serve(lConn, "[HTTPS]", rConn.RemoteAddr().String(), initPkt.Domain(), pxy.timeout) + + if pxy.patternExists() && !pxy.patternMatches([]byte(initPkt.Domain())) { + if _, err := rConn.Write(chPkt.Raw()); err != nil { + log.Debug("[HTTPS] Error writing client hello to ", initPkt.Domain(), err) + return + } + } else { + chunks := pxy.splitInChunks(chPkt.Raw(), pxy.windowSize) + if _, err := rConn.WriteChunks(chunks); err != nil { + log.Debug("[HTTPS] Error writing client hello to ", initPkt.Domain(), err) + return + } + } + + lConn.Serve(rConn, "[HTTPS]", lConn.RemoteAddr().String(), initPkt.Domain(), pxy.timeout) +} + +func (pxy *Proxy) splitInChunks(bytes []byte, size int) [][]byte { + // If the packet matches the pattern or the URLs, we don't split it + if pxy.patternExists() && !pxy.patternMatches(bytes) { + return [][]byte{bytes} + } + + var chunks [][]byte + var raw []byte = bytes + + for { + if len(raw) == 0 { + break + } + + // necessary check to avoid slicing beyond + // slice capacity + if len(raw) < size { + size = len(raw) + } + + chunks = append(chunks, raw[0:size]) + raw = raw[size:] + } + + return chunks +} + +func (pxy *Proxy) patternExists() bool { + return pxy.allowedPattern != nil || pxy.allowedUrls != nil +} + +func (p *Proxy) patternMatches(bytes []byte) bool { + return (p.allowedPattern != nil && p.allowedPattern.Match(bytes)) || + (p.allowedUrls != nil && p.allowedUrls.Match(bytes)) +} diff --git a/proxy/proxy.go b/proxy/proxy.go index 7831f41..f1c5562 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -3,6 +3,7 @@ package proxy import ( "fmt" "os" + "regexp" log "github.com/sirupsen/logrus" "github.com/xvzc/SpoofDPI/dns" @@ -12,39 +13,45 @@ import ( ) type Proxy struct { - addr string - port int - timeout int - resolver *dns.DnsResolver + addr string + port int + timeout int + resolver *dns.DnsResolver + windowSize int + allowedPattern *regexp.Regexp + allowedUrls *regexp.Regexp } func New(config *util.Config) *Proxy { return &Proxy{ - addr: *config.Addr, - port: *config.Port, - timeout: *config.Timeout, - resolver: dns.NewResolver(config), + addr: *config.Addr, + port: *config.Port, + timeout: *config.Timeout, + windowSize: *config.WindowSize, + allowedPattern: config.AllowedPattern, + allowedUrls: config.AllowedUrls, + resolver: dns.NewResolver(config), } } -func (p *Proxy) TcpAddr() *net.TCPAddr { - return net.TcpAddr(p.addr, p.port) +func (pxy *Proxy) TcpAddr() *net.TCPAddr { + return net.TcpAddr(pxy.addr, pxy.port) } -func (p *Proxy) Port() int { - return p.port +func (pxy *Proxy) Port() int { + return pxy.port } -func (p *Proxy) Start() { - l, err := net.ListenTCP("tcp4", p.TcpAddr()) +func (pxy *Proxy) Start() { + l, err := net.ListenTCP("tcp4", pxy.TcpAddr()) if err != nil { log.Fatal("Error creating listener: ", err) os.Exit(1) } - log.Println(fmt.Sprintf("Connection timeout is set to %dms", p.timeout)) + log.Println(fmt.Sprintf("Connection timeout is set to %dms", pxy.timeout)) - log.Println("Created a listener on port", p.Port()) + log.Println("Created a listener on port", pxy.Port()) for { conn, err := l.Accept() @@ -64,20 +71,30 @@ func (p *Proxy) Start() { pkt, err := packet.NewHttpPacket(b) if err != nil { log.Debug("Error while parsing request: ", string(b)) + conn.Close() return } if !pkt.IsValidMethod() { log.Debug("Unsupported method: ", pkt.Method()) + conn.Close() + return + } + + ip, err := pxy.resolver.Lookup(pkt.Domain()) + if err != nil { + log.Error("[HTTP] Error looking up for domain with ", pkt.Domain(), " ", err) + conn.Write([]byte(pkt.Version() + " 502 Bad Gateway\r\n\r\n")) + conn.Close() return } if pkt.IsConnectMethod() { log.Debug("[HTTPS] Start") - conn.HandleHttps(pkt, p.timeout, p.resolver) + pxy.HandleHttps(conn, pkt, ip) } else { log.Debug("[HTTP] Start") - conn.HandleHttp(pkt, p.timeout, p.resolver) + pxy.HandleHttp(conn, pkt, ip) } }() } diff --git a/util/config.go b/util/config.go index 57e82ca..37b6c55 100644 --- a/util/config.go +++ b/util/config.go @@ -22,6 +22,7 @@ type Config struct { Timeout *int AllowedPattern *regexp.Regexp AllowedUrls *regexp.Regexp + WindowSize *int } type ArrayFlags []string @@ -43,15 +44,6 @@ func GetConfig() *Config { return config } -func (c *Config) PatternExists() bool { - return c.AllowedPattern != nil || c.AllowedUrls != nil -} - -func (c *Config) PatternMatches(bytes []byte) bool { - return (c.AllowedPattern != nil && c.AllowedPattern.Match(bytes)) || - (c.AllowedUrls != nil && c.AllowedUrls.Match(bytes)) -} - func ParseArgs() { config = &Config{} config.Addr = flag.String("addr", "127.0.0.1", "Listen addr") @@ -62,6 +54,7 @@ func ParseArgs() { config.Debug = flag.Bool("debug", false, "Enable debug output") config.NoBanner = flag.Bool("no-banner", false, "Disable banner") config.Timeout = flag.Int("timeout", 2000, "timeout in milliseconds") + config.WindowSize = flag.Int("window-size", 50, "window-size for fragmented client hello") flag.Var(&allowedHosts, "url", "Bypass DPI only on this url, can be passed multiple times") allowedPattern = flag.String( @@ -88,8 +81,8 @@ func ParseArgs() { } func PrintColoredBanner() { - cyan := putils.LettersFromStringWithStyle("Spoof", pterm.NewStyle(pterm.FgCyan)) - purple := putils.LettersFromStringWithStyle("DPI", pterm.NewStyle(pterm.FgLightMagenta)) + cyan := putils.LettersFromStringWithStyle("Spoof", pterm.NewStyle(pterm.FgCyan)) + purple := putils.LettersFromStringWithStyle("DPI", pterm.NewStyle(pterm.FgLightMagenta)) pterm.DefaultBigText.WithLetters(cyan, purple).Render() pterm.DefaultBulletList.WithItems([]pterm.BulletListItem{