use system-dns when patterns are not matched

This commit is contained in:
xvzc 2024-08-06 17:48:18 +09:00
parent 24c48c6c9a
commit 641ded49d8
6 changed files with 93 additions and 87 deletions

View File

@ -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")
}
}

View File

@ -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)")
}

View File

@ -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 &&

View File

@ -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))
}

View File

@ -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 {

View File

@ -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() {