diff --git a/cmd/spoof-dpi/main.go b/cmd/spoof-dpi/main.go index 223bd5d..0398897 100644 --- a/cmd/spoof-dpi/main.go +++ b/cmd/spoof-dpi/main.go @@ -41,7 +41,7 @@ func main() { if *config.SystemProxy { if err := util.SetOsProxy(*config.Port); err != nil { - log.Fatal(err) + log.Fatal("Error while changing proxy settings") } } diff --git a/dns/dns.go b/dns/dns.go index deb174b..9ba0d3e 100644 --- a/dns/dns.go +++ b/dns/dns.go @@ -3,6 +3,7 @@ package dns import ( "context" "errors" + "net" "regexp" "strconv" "time" @@ -28,18 +29,30 @@ func NewResolver(config *util.Config) *DnsResolver { } } -func (d *DnsResolver) Lookup(domain string) (string, error) { +func (d *DnsResolver) Lookup(domain string, useSystemDns bool) (string, error) { ipRegex := "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$" if r, _ := regexp.MatchString(ipRegex, domain); r { return domain, nil } + if useSystemDns { + log.Debug("[DNS] ", domain, " resolving with system dns") + return systemLookup(domain) + } + if d.enableDoh { + log.Debug("[DNS] ", domain, " resolving with dns over https") return dohLookup(domain) } - dnsServer := d.host + ":" + d.port + log.Debug("[DNS] ", domain, " resolving with custom dns") + return customLookup(d.host, d.port, domain) +} + +func customLookup(host string, port string, domain string) (string, error) { + + dnsServer := host + ":" + port msg := new(dns.Msg) msg.SetQuestion(dns.Fqdn(domain), dns.TypeA) @@ -48,17 +61,31 @@ func (d *DnsResolver) Lookup(domain string) (string, error) { response, _, err := c.Exchange(msg, dnsServer) if err != nil { - return "", errors.New("couldn not resolve the domain") + return "", errors.New("couldn not resolve the domain(custom)") } for _, answer := range response.Answer { if record, ok := answer.(*dns.A); ok { - log.Debug("[DNS] resolved ", domain, ": ", record.A.String()) return record.A.String(), nil } } - return "", errors.New("no record found") + return "", errors.New("no record found(custom)") + +} + +func systemLookup(domain string) (string, error) { + systemResolver := net.Resolver{PreferGo: true} + ips, err := systemResolver.LookupIPAddr(context.Background(), domain) + if err != nil { + return "", errors.New("couldn not resolve the domain(system)") + } + + for _, ip := range ips { + return ip.String(), nil + } + + return "", errors.New("no record found(system)") } func dohLookup(domain string) (string, error) { @@ -68,7 +95,7 @@ func dohLookup(domain string) (string, error) { rsp, err := c.Query(ctx, dohDns.Domain(domain), dohDns.TypeA) if err != nil { - return "", errors.New("could not resolve the domain") + return "", errors.New("could not resolve the domain(doh)") } // doh dns answer answer := rsp.Answer @@ -79,12 +106,11 @@ func dohLookup(domain string) (string, error) { continue } - log.Debug("[DOH] resolved ", domain, ": ", a.Data) return a.Data, nil } // close the client c.Close() - return "", errors.New("no record found") + return "", errors.New("no record found(doh)") } diff --git a/make-releases.sh b/make-releases.sh index 751319b..fff1e42 100644 --- a/make-releases.sh +++ b/make-releases.sh @@ -1,6 +1,6 @@ #!/bin/bash -VERSION="v0.10.5" +VERSION="v0.10.6" for osarch in 'darwin/amd64' 'darwin/arm64' 'linux/amd64' 'linux/arm' 'linux/arm64' 'linux/mips' 'linux/mipsle'; do GOOS=${osarch%/*} GOARCH=${osarch#*/} go build -ldflags="-w -s -X main.VERSION=${VERSION}" github.com/xvzc/SpoofDPI/cmd/spoof-dpi && diff --git a/proxy/https.go b/proxy/https.go index b129941..0cef83a 100644 --- a/proxy/https.go +++ b/proxy/https.go @@ -8,7 +8,7 @@ import ( "github.com/xvzc/SpoofDPI/packet" ) -func (pxy *Proxy) handleHttps(lConn *net.TCPConn, initPkt *packet.HttpPacket, ip string) { +func (pxy *Proxy) handleHttps(lConn *net.TCPConn, exploit bool, initPkt *packet.HttpPacket, ip string) { // Create a connection to the requested server var port int = 443 var err error @@ -62,17 +62,17 @@ func (pxy *Proxy) handleHttps(lConn *net.TCPConn, initPkt *packet.HttpPacket, ip go Serve(rConn, lConn, "[HTTPS]", rConn.RemoteAddr().String(), initPkt.Domain(), pxy.timeout) - if pxy.patternExists() && !pxy.patternMatches([]byte(initPkt.Domain())) { - log.Debug("[HTTPS] Writing plain client hello to ", initPkt.Domain()) - if _, err := rConn.Write(chPkt.Raw()); err != nil { - log.Debug("[HTTPS] Error writing plain client hello to ", initPkt.Domain(), err) + if exploit { + log.Debug("[HTTPS] Writing chunked client hello to ", initPkt.Domain()) + chunks := splitInChunks(chPkt.Raw(), pxy.windowSize) + if _, err := WriteChunks(rConn, chunks); err != nil { + log.Debug("[HTTPS] Error writing chunked client hello to ", initPkt.Domain(), err) return } } else { - log.Debug("[HTTPS] Writing chunked client hello to ", initPkt.Domain()) - chunks := pxy.splitInChunks(chPkt.Raw()) - if _, err := WriteChunks(rConn, chunks); err != nil { - log.Debug("[HTTPS] Error writing chunked client hello to ", initPkt.Domain(), err) + log.Debug("[HTTPS] Writing plain client hello to ", initPkt.Domain()) + if _, err := rConn.Write(chPkt.Raw()); err != nil { + log.Debug("[HTTPS] Error writing plain client hello to ", initPkt.Domain(), err) return } } @@ -80,17 +80,11 @@ func (pxy *Proxy) handleHttps(lConn *net.TCPConn, initPkt *packet.HttpPacket, ip Serve(lConn, rConn, "[HTTPS]", lConn.RemoteAddr().String(), initPkt.Domain(), pxy.timeout) } -func (pxy *Proxy) splitInChunks(bytes []byte) [][]byte { - // If the packet matches the pattern or the URLs, we don't split it - if pxy.patternExists() && !pxy.patternMatches(bytes) { - return [][]byte{bytes} - } - +func splitInChunks(bytes []byte, size int) [][]byte { var chunks [][]byte var raw []byte = bytes - var size = pxy.windowSize - log.Debug("[HTTPS] window-size: ", size) + log.Debug("[HTTPS] window-size: ", size) if size > 0 { for { @@ -111,22 +105,13 @@ func (pxy *Proxy) splitInChunks(bytes []byte) [][]byte { return chunks } - // When the given window-size <= 0 + // When the given window-size <= 0 if len(raw) < 1 { return [][]byte{raw} } - log.Debug("[HTTPS] Using legacy fragmentation.") + log.Debug("[HTTPS] Using legacy fragmentation.") return [][]byte{raw[:1], raw[1:]} } - -func (pxy *Proxy) patternExists() bool { - return pxy.allowedPattern != nil || pxy.allowedUrls != nil -} - -func (pxy *Proxy) patternMatches(bytes []byte) bool { - return (pxy.allowedPattern != nil && pxy.allowedPattern.Match(bytes)) || - (pxy.allowedUrls != nil && pxy.allowedUrls.Match(bytes)) -} diff --git a/proxy/proxy.go b/proxy/proxy.go index 3cdf451..24b16e6 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -19,8 +19,7 @@ type Proxy struct { timeout int resolver *dns.DnsResolver windowSize int - allowedPattern *regexp.Regexp - allowedUrls *regexp.Regexp + allowedPattern []*regexp.Regexp } func New(config *util.Config) *Proxy { @@ -30,7 +29,6 @@ func New(config *util.Config) *Proxy { timeout: *config.Timeout, windowSize: *config.WindowSize, allowedPattern: config.AllowedPattern, - allowedUrls: config.AllowedUrls, resolver: dns.NewResolver(config), } } @@ -42,11 +40,14 @@ func (pxy *Proxy) Start() { os.Exit(1) } - if pxy.timeout > 0 { - log.Println(fmt.Sprintf("[PROXY] Connection timeout is set to %dms", pxy.timeout)) - } + if pxy.timeout > 0 { + log.Println(fmt.Sprintf("[PROXY] Connection timeout is set to %dms", pxy.timeout)) + } log.Println("[PROXY] Created a listener on port", pxy.port) + if len(pxy.allowedPattern) > 0 { + log.Println("[PROXY] Number of white-listed pattern:", len(pxy.allowedPattern)) + } for { conn, err := l.Accept() @@ -76,9 +77,12 @@ func (pxy *Proxy) Start() { return } - ip, err := pxy.resolver.Lookup(pkt.Domain()) + matched := pxy.patternMatches([]byte(pkt.Domain())) + useSystemDns := !matched + + ip, err := pxy.resolver.Lookup(pkt.Domain(), useSystemDns) if err != nil { - log.Debug("[PROXY] Error while dns lookup: ", pkt.Domain(), " ", err) + log.Debug("[PROXY] Error while dns lookup: ", pkt.Domain(), " ", err) conn.Write([]byte(pkt.Version() + " 502 Bad Gateway\r\n\r\n")) conn.Close() return @@ -93,7 +97,7 @@ func (pxy *Proxy) Start() { if pkt.IsConnectMethod() { log.Debug("[PROXY] Start HTTPS") - pxy.handleHttps(conn.(*net.TCPConn), pkt, ip) + pxy.handleHttps(conn.(*net.TCPConn), matched, pkt, ip) } else { log.Debug("[PROXY] Start HTTP") pxy.handleHttp(conn.(*net.TCPConn), pkt, ip) @@ -102,6 +106,20 @@ func (pxy *Proxy) Start() { } } +func (pxy *Proxy) patternMatches(bytes []byte) bool { + if pxy.allowedPattern == nil { + return true + } + + for _, pattern := range pxy.allowedPattern { + if pattern.Match(bytes) { + return true + } + } + + return false +} + func isLoopedRequest(ip net.IP) bool { // we don't handle IPv6 at all it seems if ip.To4() == nil { diff --git a/util/config.go b/util/config.go index 937e504..aca391c 100644 --- a/util/config.go +++ b/util/config.go @@ -4,11 +4,9 @@ import ( "flag" "fmt" "regexp" - "strings" "github.com/pterm/pterm" "github.com/pterm/pterm/putils" - log "github.com/sirupsen/logrus" ) type Config struct { @@ -21,26 +19,23 @@ type Config struct { NoBanner *bool SystemProxy *bool Timeout *int - AllowedPattern *regexp.Regexp - AllowedUrls *regexp.Regexp + AllowedPattern []*regexp.Regexp WindowSize *int Version *bool } -type ArrayFlags []string +type StringArray []string -func (i *ArrayFlags) String() string { - return "my string representation" +func (arr *StringArray) String() string { + return fmt.Sprintf("%s", *arr) } -func (i *ArrayFlags) Set(value string) error { - *i = append(*i, value) +func (arr *StringArray) Set(value string) error { + *arr = append(*arr, value) return nil } var config *Config -var allowedHosts ArrayFlags -var allowedPattern *string func GetConfig() *Config { return config @@ -52,38 +47,28 @@ func ParseArgs() { config.Port = flag.Int("port", 8080, "port") config.DnsAddr = flag.String("dns-addr", "8.8.8.8", "dns address") config.DnsPort = flag.Int("dns-port", 53, "port number for dns") - config.EnableDoh = flag.Bool("enable-doh", false, "enable 'dns over https'") + config.EnableDoh = flag.Bool("enable-doh", false, "enable 'dns-over-https'") config.Debug = flag.Bool("debug", false, "enable debug output") config.NoBanner = flag.Bool("no-banner", false, "disable banner") config.SystemProxy = flag.Bool("system-proxy", true, "enable system-wide proxy") - config.Timeout = flag.Int("timeout", 0, "timeout in milliseconds. no timeout when not given") + config.Timeout = flag.Int("timeout", 0, "timeout in milliseconds; no timeout when not given") config.WindowSize = flag.Int("window-size", 0, `chunk size, in number of bytes, for fragmented client hello, try lower values if the default value doesn't bypass the DPI; when not given, the client hello packet will be sent in two parts: fragmentation for the first data packet and the rest `) - flag.Var(&allowedHosts, "url", "Bypass DPI only on this url, can be passed multiple times") - allowedPattern = flag.String( - "pattern", - "", - "bypass DPI only on packets matching this regex pattern", - ) - config.Version = flag.Bool("v", false, "print spoof-dpi's version. this may contain some other relevant information") + config.Version = flag.Bool("v", false, "print spoof-dpi's version; this may contain some other relevant information") + var allowedPattern StringArray + flag.Var( + &allowedPattern, + "pattern", + "bypass DPI only on packets matching this regex pattern; can be given multiple times", + ) flag.Parse() - if len(allowedHosts) > 0 { - var escapedUrls []string - for _, host := range allowedHosts { - escapedUrls = append(escapedUrls, regexp.QuoteMeta(host)) - } - - allowedHostsRegex := strings.Join(escapedUrls, "|") - config.AllowedUrls = regexp.MustCompile(allowedHostsRegex) - } - - if *allowedPattern != "" { - config.AllowedPattern = regexp.MustCompile(*allowedPattern) + for _, pattern := range allowedPattern { + config.AllowedPattern = append(config.AllowedPattern, regexp.MustCompile(pattern)) } } @@ -98,14 +83,6 @@ func PrintColoredBanner() { {Level: 0, Text: "DNS : " + fmt.Sprint(*config.DnsAddr)}, {Level: 0, Text: "DEBUG : " + fmt.Sprint(*config.Debug)}, }).Render() - - if allowedHosts != nil && len(allowedHosts) > 0 { - log.Info("White listed urls: ", allowedHosts) - } - - if *allowedPattern != "" { - log.Info("Regex Pattern: ", *allowedPattern) - } } func PrintSimpleInfo() {