From 7bf3f9e4e5703a10bfedc05e38f1286d710695bc Mon Sep 17 00:00:00 2001 From: Andrey Semenov Date: Thu, 8 Aug 2024 23:47:34 +0500 Subject: [PATCH 01/14] perf: reuse allocated buffer. add config parameter --- proxy/http.go | 4 +-- proxy/https.go | 7 ++--- proxy/io.go | 71 +++++++++++++++++++++++--------------------------- proxy/proxy.go | 17 +++++++----- util/config.go | 3 ++- 5 files changed, 51 insertions(+), 51 deletions(-) diff --git a/proxy/http.go b/proxy/http.go index 35d5c1d..183b0d6 100644 --- a/proxy/http.go +++ b/proxy/http.go @@ -39,7 +39,7 @@ func (pxy *Proxy) handleHttp(lConn *net.TCPConn, pkt *packet.HttpPacket, ip stri log.Debug("[HTTP] New connection to the server ", pkt.Domain(), " ", rConn.LocalAddr()) - go Serve(rConn, lConn, "[HTTP]", lConn.RemoteAddr().String(), pkt.Domain(), pxy.timeout) + go Serve(rConn, lConn, "[HTTP]", lConn.RemoteAddr().String(), pkt.Domain(), pxy.timeout, pxy.bufferSize) _, err = rConn.Write(pkt.Raw()) if err != nil { @@ -49,5 +49,5 @@ func (pxy *Proxy) handleHttp(lConn *net.TCPConn, pkt *packet.HttpPacket, ip stri log.Debug("[HTTP] Sent a request to ", pkt.Domain()) - Serve(lConn, rConn, "[HTTP]", lConn.RemoteAddr().String(), pkt.Domain(), pxy.timeout) + Serve(lConn, rConn, "[HTTP]", lConn.RemoteAddr().String(), pkt.Domain(), pxy.timeout, pxy.bufferSize) } diff --git a/proxy/https.go b/proxy/https.go index 0cef83a..634f671 100644 --- a/proxy/https.go +++ b/proxy/https.go @@ -45,7 +45,8 @@ func (pxy *Proxy) handleHttps(lConn *net.TCPConn, exploit bool, initPkt *packet. log.Debug("[HTTPS] Sent 200 Connection Estabalished to ", lConn.RemoteAddr()) // Read client hello - clientHello, err := ReadBytes(lConn) + tmpBuffer := make([]byte, pxy.bufferSize) + clientHello, err := ReadBytes(lConn, tmpBuffer) if err != nil { log.Debug("[HTTPS] Error reading client hello from the client", err) return @@ -60,7 +61,7 @@ func (pxy *Proxy) handleHttps(lConn *net.TCPConn, exploit bool, initPkt *packet. // lConn.SetLinger(3) // rConn.SetLinger(3) - go Serve(rConn, lConn, "[HTTPS]", rConn.RemoteAddr().String(), initPkt.Domain(), pxy.timeout) + go Serve(rConn, lConn, "[HTTPS]", rConn.RemoteAddr().String(), initPkt.Domain(), pxy.timeout, pxy.bufferSize) if exploit { log.Debug("[HTTPS] Writing chunked client hello to ", initPkt.Domain()) @@ -77,7 +78,7 @@ func (pxy *Proxy) handleHttps(lConn *net.TCPConn, exploit bool, initPkt *packet. } } - Serve(lConn, rConn, "[HTTPS]", lConn.RemoteAddr().String(), initPkt.Domain(), pxy.timeout) + Serve(lConn, rConn, "[HTTPS]", lConn.RemoteAddr().String(), initPkt.Domain(), pxy.timeout, pxy.bufferSize) } func splitInChunks(bytes []byte, size int) [][]byte { diff --git a/proxy/io.go b/proxy/io.go index a8a1fff..f166bb3 100644 --- a/proxy/io.go +++ b/proxy/io.go @@ -9,8 +9,6 @@ import ( log "github.com/sirupsen/logrus" ) -const BUF_SIZE = 1024 - func WriteChunks(conn *net.TCPConn, c [][]byte) (n int, err error) { total := 0 for i := 0; i < len(c); i++ { @@ -25,45 +23,42 @@ func WriteChunks(conn *net.TCPConn, c [][]byte) (n int, err error) { return total, nil } -func ReadBytes(conn *net.TCPConn) ([]byte, error) { - ret := make([]byte, 0) - buf := make([]byte, BUF_SIZE) - - for { - n, err := conn.Read(buf) - if err != nil { - switch err.(type) { - case *net.OpError: - return nil, errors.New("timed out") - default: - return nil, err - } - } - ret = append(ret, buf[:n]...) - - if n < BUF_SIZE { - break - } - } - - if len(ret) == 0 { - return nil, io.EOF - } - - return ret, nil +func ReadBytes(conn *net.TCPConn, dest []byte) ([]byte, error) { + n, err := readBytesInternal(conn, dest) + return dest[:n], err } -func Serve(from *net.TCPConn, to *net.TCPConn, proto string, fd string, td string, timeout int) { - proto += " " - +func readBytesInternal(in io.Reader, dest []byte) (int, error) { + totalRead := 0 for { - if timeout > 0 { - from.SetReadDeadline( - time.Now().Add(time.Millisecond * time.Duration(timeout)), - ) - } + numRead, readErr := in.Read(dest[totalRead:]) + totalRead += numRead + if readErr != nil { + switch readErr.(type) { + case *net.OpError: + return totalRead, errors.New("timed out") + default: + return totalRead, readErr + } + } + if totalRead == 0 { + return 0, io.EOF + } + return totalRead, nil + } +} - buf, err := ReadBytes(from) +func Serve(from *net.TCPConn, to *net.TCPConn, proto string, fd string, td string, timeout int, bufferSize int) { + proto += " " + buf := make([]byte, bufferSize) + for { + if timeout > 0 { + from.SetReadDeadline( + time.Now().Add(time.Millisecond * time.Duration(timeout)), + ) + } + + bytesRead, err := ReadBytes(from, buf) if err != nil { if err == io.EOF { log.Debug(proto, "Finished ", fd) @@ -73,7 +68,7 @@ func Serve(from *net.TCPConn, to *net.TCPConn, proto string, fd string, td strin return } - if _, err := to.Write(buf); err != nil { + if _, err := to.Write(bytesRead); err != nil { log.Debug(proto, "Error Writing to ", td) return } diff --git a/proxy/proxy.go b/proxy/proxy.go index 24b16e6..d4301ae 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -20,6 +20,7 @@ type Proxy struct { resolver *dns.DnsResolver windowSize int allowedPattern []*regexp.Regexp + bufferSize int } func New(config *util.Config) *Proxy { @@ -30,6 +31,7 @@ func New(config *util.Config) *Proxy { windowSize: *config.WindowSize, allowedPattern: config.AllowedPattern, resolver: dns.NewResolver(config), + bufferSize: *config.BufferSize, } } @@ -46,7 +48,7 @@ func (pxy *Proxy) Start() { 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)) + log.Println("[PROXY] Number of white-listed pattern:", len(pxy.allowedPattern)) } for { @@ -57,7 +59,8 @@ func (pxy *Proxy) Start() { } go func() { - b, err := ReadBytes(conn.(*net.TCPConn)) + tmpBuf := make([]byte, pxy.bufferSize) + b, err := ReadBytes(conn.(*net.TCPConn), tmpBuf) if err != nil { return } @@ -77,8 +80,8 @@ func (pxy *Proxy) Start() { return } - matched := pxy.patternMatches([]byte(pkt.Domain())) - useSystemDns := !matched + matched := pxy.patternMatches([]byte(pkt.Domain())) + useSystemDns := !matched ip, err := pxy.resolver.Lookup(pkt.Domain(), useSystemDns) if err != nil { @@ -113,11 +116,11 @@ func (pxy *Proxy) patternMatches(bytes []byte) bool { for _, pattern := range pxy.allowedPattern { if pattern.Match(bytes) { - return true - } + return true + } } - return false + return false } func isLoopedRequest(ip net.IP) bool { diff --git a/util/config.go b/util/config.go index aca391c..2b5a9d1 100644 --- a/util/config.go +++ b/util/config.go @@ -22,6 +22,7 @@ type Config struct { AllowedPattern []*regexp.Regexp WindowSize *int Version *bool + BufferSize *int } type StringArray []string @@ -58,7 +59,7 @@ when not given, the client hello packet will be sent in two parts: fragmentation for the first data packet and the rest `) config.Version = flag.Bool("v", false, "print spoof-dpi's version; this may contain some other relevant information") - + config.BufferSize = flag.Int("buffer-size", 1024, "buffer size, in number of bytes, is the maximum amount of data that can be read at once from a remote resource") var allowedPattern StringArray flag.Var( &allowedPattern, From 884389256db6afb2e9ee2a493ef441b80832559e Mon Sep 17 00:00:00 2001 From: Andrey Semenov Date: Fri, 9 Aug 2024 01:52:59 +0500 Subject: [PATCH 02/14] perf: remove loop --- proxy/io.go | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/proxy/io.go b/proxy/io.go index f166bb3..bb58c7e 100644 --- a/proxy/io.go +++ b/proxy/io.go @@ -28,24 +28,17 @@ func ReadBytes(conn *net.TCPConn, dest []byte) ([]byte, error) { return dest[:n], err } -func readBytesInternal(in io.Reader, dest []byte) (int, error) { - totalRead := 0 - for { - numRead, readErr := in.Read(dest[totalRead:]) - totalRead += numRead - if readErr != nil { - switch readErr.(type) { - case *net.OpError: - return totalRead, errors.New("timed out") - default: - return totalRead, readErr - } +func readBytesInternal(conn *net.TCPConn, dest []byte) (int, error) { + totalRead, err := conn.Read(dest) + if err != nil { + switch err.(type) { + case *net.OpError: + return totalRead, errors.New("timed out") + default: + return totalRead, err } - if totalRead == 0 { - return 0, io.EOF - } - return totalRead, nil } + return totalRead, nil } func Serve(from *net.TCPConn, to *net.TCPConn, proto string, fd string, td string, timeout int, bufferSize int) { From 22d6a064faee37beaf0432a6fa7335d0358a57e3 Mon Sep 17 00:00:00 2001 From: Andrey Semenov Date: Fri, 9 Aug 2024 03:07:29 +0500 Subject: [PATCH 03/14] Parse request from connection instead of buffer --- packet/http.go | 21 ++++++++++++--------- proxy/proxy.go | 8 +++----- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/packet/http.go b/packet/http.go index 2e75260..5ca57f3 100644 --- a/packet/http.go +++ b/packet/http.go @@ -2,6 +2,7 @@ package packet import ( "bufio" + "io" "net" "net/http" "strings" @@ -56,12 +57,16 @@ func ParseUrl(raw []byte) { } -func NewHttpPacket(raw []byte) (*HttpPacket, error) { - pkt := &HttpPacket{raw: raw} - - pkt.parse() - - return pkt, nil +func NewHttpPacketFromReader(rdr io.Reader) (*HttpPacket, error) { + sb := strings.Builder{} + tee := io.TeeReader(rdr, &sb) + p := &HttpPacket{} + err := parse(p, bufio.NewReader(tee)) + if err != nil { + return nil, err + } + p.raw = []byte(sb.String()) + return p, nil } func (p *HttpPacket) Raw() []byte { @@ -123,8 +128,7 @@ func (p *HttpPacket) Tidy() { p.raw = []byte(result) } -func (p *HttpPacket) parse() error { - reader := bufio.NewReader(strings.NewReader(string(p.raw))) +func parse(p *HttpPacket, reader *bufio.Reader) error { request, err := http.ReadRequest(reader) if err != nil { return err @@ -152,6 +156,5 @@ func (p *HttpPacket) parse() error { } request.Body.Close() - return nil } diff --git a/proxy/proxy.go b/proxy/proxy.go index d4301ae..01c05fa 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -59,17 +59,15 @@ func (pxy *Proxy) Start() { } go func() { - tmpBuf := make([]byte, pxy.bufferSize) - b, err := ReadBytes(conn.(*net.TCPConn), tmpBuf) + pkt, err := packet.NewHttpPacketFromReader(conn) if err != nil { return } - log.Debug("[PROXY] Request from ", conn.RemoteAddr(), "\n\n", string(b)) + log.Debug("[PROXY] Request from ", conn.RemoteAddr(), "\n\n", string(pkt.Raw())) - pkt, err := packet.NewHttpPacket(b) if err != nil { - log.Debug("[PROXY] Error while parsing request: ", string(b)) + log.Debug("[PROXY] Error while parsing request: ", string(pkt.Raw())) conn.Close() return } From faef0aa0461b6c5b41a975993d3538c9985c7b4d Mon Sep 17 00:00:00 2001 From: Andrey Semenov Date: Sat, 10 Aug 2024 21:19:52 +0500 Subject: [PATCH 04/14] remove buffer-size parameter --- proxy/proxy.go | 4 +++- util/config.go | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/proxy/proxy.go b/proxy/proxy.go index 01c05fa..f1a7701 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -13,6 +13,8 @@ import ( "github.com/xvzc/SpoofDPI/util" ) +const BUFFER_SIZE = 1024 + type Proxy struct { addr string port int @@ -31,7 +33,7 @@ func New(config *util.Config) *Proxy { windowSize: *config.WindowSize, allowedPattern: config.AllowedPattern, resolver: dns.NewResolver(config), - bufferSize: *config.BufferSize, + bufferSize: BUFFER_SIZE, } } diff --git a/util/config.go b/util/config.go index 2b5a9d1..27ec576 100644 --- a/util/config.go +++ b/util/config.go @@ -22,7 +22,6 @@ type Config struct { AllowedPattern []*regexp.Regexp WindowSize *int Version *bool - BufferSize *int } type StringArray []string @@ -59,7 +58,6 @@ when not given, the client hello packet will be sent in two parts: fragmentation for the first data packet and the rest `) config.Version = flag.Bool("v", false, "print spoof-dpi's version; this may contain some other relevant information") - config.BufferSize = flag.Int("buffer-size", 1024, "buffer size, in number of bytes, is the maximum amount of data that can be read at once from a remote resource") var allowedPattern StringArray flag.Var( &allowedPattern, From 4c0f5ca6c8c6313382ff4655f8bfdea890be8796 Mon Sep 17 00:00:00 2001 From: LiquidTheDangerous Date: Sun, 11 Aug 2024 16:44:34 +0500 Subject: [PATCH 05/14] fix: client hello --- proxy/https.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/https.go b/proxy/https.go index 634f671..359336c 100644 --- a/proxy/https.go +++ b/proxy/https.go @@ -45,7 +45,7 @@ func (pxy *Proxy) handleHttps(lConn *net.TCPConn, exploit bool, initPkt *packet. log.Debug("[HTTPS] Sent 200 Connection Estabalished to ", lConn.RemoteAddr()) // Read client hello - tmpBuffer := make([]byte, pxy.bufferSize) + tmpBuffer := make([]byte, 4096) clientHello, err := ReadBytes(lConn, tmpBuffer) if err != nil { log.Debug("[HTTPS] Error reading client hello from the client", err) From 1f7fe642d58dd53ea05ffaecdf8db7ad6702a2f9 Mon Sep 17 00:00:00 2001 From: LiquidTheDangerous Date: Sun, 11 Aug 2024 18:54:06 +0500 Subject: [PATCH 06/14] fix: client hello --- proxy/client_hello.go | 48 +++++++++++++++++++++++++++++++++++++++++++ proxy/https.go | 4 ++-- 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 proxy/client_hello.go diff --git a/proxy/client_hello.go b/proxy/client_hello.go new file mode 100644 index 0000000..102b7c8 --- /dev/null +++ b/proxy/client_hello.go @@ -0,0 +1,48 @@ +package proxy + +import ( + "encoding/binary" + "io" +) + +const headerLen = 5 + +type ClientHello struct { + Header ClientHelloHeader + Raw []byte //Header + Payload + RawHeader []byte + RawPayload []byte +} + +type ClientHelloHeader struct { + Type byte + ProtoVersion uint16 + PayloadLen uint16 +} + +func ReadClientHello(r io.Reader) (*ClientHello, error) { + var rawHeader [5]byte + _, err := io.ReadFull(r, rawHeader[:]) + if err != nil { + return nil, err + } + + header := ClientHelloHeader{ + Type: rawHeader[0], + ProtoVersion: binary.BigEndian.Uint16(rawHeader[1:3]), + PayloadLen: binary.BigEndian.Uint16(rawHeader[3:5]), + } + raw := make([]byte, header.PayloadLen+headerLen) + copy(raw[0:headerLen], rawHeader[:]) + _, err = io.ReadFull(r, raw[headerLen:]) + if err != nil { + return nil, err + } + hello := &ClientHello{ + Header: header, + Raw: raw, + } + hello.RawHeader = hello.Raw[:headerLen] + hello.RawPayload = hello.Raw[headerLen:] + return hello, nil +} diff --git a/proxy/https.go b/proxy/https.go index 359336c..1ef49f8 100644 --- a/proxy/https.go +++ b/proxy/https.go @@ -45,12 +45,12 @@ func (pxy *Proxy) handleHttps(lConn *net.TCPConn, exploit bool, initPkt *packet. log.Debug("[HTTPS] Sent 200 Connection Estabalished to ", lConn.RemoteAddr()) // Read client hello - tmpBuffer := make([]byte, 4096) - clientHello, err := ReadBytes(lConn, tmpBuffer) + hello, err := ReadClientHello(lConn) if err != nil { log.Debug("[HTTPS] Error reading client hello from the client", err) return } + clientHello := hello.Raw log.Debug("[HTTPS] Client sent hello ", len(clientHello), "bytes") From fa064736a89b4add380b63581765d7c07be29975 Mon Sep 17 00:00:00 2001 From: LiquidTheDangerous Date: Wed, 14 Aug 2024 02:33:58 +0500 Subject: [PATCH 07/14] refactor --- proxy/client_hello.go | 44 ++++++++++++++++++++++++++++++------------- proxy/https.go | 6 +++--- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/proxy/client_hello.go b/proxy/client_hello.go index 102b7c8..09d1f57 100644 --- a/proxy/client_hello.go +++ b/proxy/client_hello.go @@ -7,28 +7,39 @@ import ( const headerLen = 5 -type ClientHello struct { - Header ClientHelloHeader +type TLSMessageType byte + +const ( + TLSInvalid TLSMessageType = 0x0 + TLSChangeCipherSpec TLSMessageType = 0x14 + TLSAlert TLSMessageType = 0x15 + TLSHandshake TLSMessageType = 0x16 + TLSApplicationData TLSMessageType = 0x17 + TLSHeartbeat TLSMessageType = 0x18 +) + +type TlsMessage struct { + Header TlsHeader Raw []byte //Header + Payload RawHeader []byte RawPayload []byte } -type ClientHelloHeader struct { - Type byte - ProtoVersion uint16 +type TlsHeader struct { + Type TLSMessageType + ProtoVersion uint16 // major | minor PayloadLen uint16 } -func ReadClientHello(r io.Reader) (*ClientHello, error) { +func ReadTlsMessage(r io.Reader) (*TlsMessage, error) { var rawHeader [5]byte _, err := io.ReadFull(r, rawHeader[:]) if err != nil { return nil, err } - header := ClientHelloHeader{ - Type: rawHeader[0], + header := TlsHeader{ + Type: TLSMessageType(rawHeader[0]), ProtoVersion: binary.BigEndian.Uint16(rawHeader[1:3]), PayloadLen: binary.BigEndian.Uint16(rawHeader[3:5]), } @@ -38,11 +49,18 @@ func ReadClientHello(r io.Reader) (*ClientHello, error) { if err != nil { return nil, err } - hello := &ClientHello{ - Header: header, - Raw: raw, + hello := &TlsMessage{ + Header: header, + Raw: raw, + RawHeader: raw[:headerLen], + RawPayload: raw[headerLen:], } - hello.RawHeader = hello.Raw[:headerLen] - hello.RawPayload = hello.Raw[headerLen:] return hello, nil } + +func IsClientHello(message *TlsMessage) bool { + // According to RFC 8446 section 4. + // first byte (Raw[5]) of handshake message should be 0x1 - means client_hello + return message.Header.Type == TLSHandshake && + message.Raw[5] == 0x1 +} diff --git a/proxy/https.go b/proxy/https.go index 1ef49f8..94eb1c4 100644 --- a/proxy/https.go +++ b/proxy/https.go @@ -45,12 +45,12 @@ func (pxy *Proxy) handleHttps(lConn *net.TCPConn, exploit bool, initPkt *packet. log.Debug("[HTTPS] Sent 200 Connection Estabalished to ", lConn.RemoteAddr()) // Read client hello - hello, err := ReadClientHello(lConn) - if err != nil { + m, err := ReadTlsMessage(lConn) + if err != nil || !IsClientHello(m) { log.Debug("[HTTPS] Error reading client hello from the client", err) return } - clientHello := hello.Raw + clientHello := m.Raw log.Debug("[HTTPS] Client sent hello ", len(clientHello), "bytes") From 10d1ee4860a8a7b84f1d38e398df9da553518d2c Mon Sep 17 00:00:00 2001 From: Andrey Semenov Date: Thu, 8 Aug 2024 23:47:34 +0500 Subject: [PATCH 08/14] perf: reuse allocated buffer. add config parameter --- proxy/http.go | 4 +-- proxy/https.go | 7 ++--- proxy/io.go | 71 +++++++++++++++++++++++--------------------------- proxy/proxy.go | 17 +++++++----- util/config.go | 3 ++- 5 files changed, 51 insertions(+), 51 deletions(-) diff --git a/proxy/http.go b/proxy/http.go index 35d5c1d..183b0d6 100644 --- a/proxy/http.go +++ b/proxy/http.go @@ -39,7 +39,7 @@ func (pxy *Proxy) handleHttp(lConn *net.TCPConn, pkt *packet.HttpPacket, ip stri log.Debug("[HTTP] New connection to the server ", pkt.Domain(), " ", rConn.LocalAddr()) - go Serve(rConn, lConn, "[HTTP]", lConn.RemoteAddr().String(), pkt.Domain(), pxy.timeout) + go Serve(rConn, lConn, "[HTTP]", lConn.RemoteAddr().String(), pkt.Domain(), pxy.timeout, pxy.bufferSize) _, err = rConn.Write(pkt.Raw()) if err != nil { @@ -49,5 +49,5 @@ func (pxy *Proxy) handleHttp(lConn *net.TCPConn, pkt *packet.HttpPacket, ip stri log.Debug("[HTTP] Sent a request to ", pkt.Domain()) - Serve(lConn, rConn, "[HTTP]", lConn.RemoteAddr().String(), pkt.Domain(), pxy.timeout) + Serve(lConn, rConn, "[HTTP]", lConn.RemoteAddr().String(), pkt.Domain(), pxy.timeout, pxy.bufferSize) } diff --git a/proxy/https.go b/proxy/https.go index 0cef83a..634f671 100644 --- a/proxy/https.go +++ b/proxy/https.go @@ -45,7 +45,8 @@ func (pxy *Proxy) handleHttps(lConn *net.TCPConn, exploit bool, initPkt *packet. log.Debug("[HTTPS] Sent 200 Connection Estabalished to ", lConn.RemoteAddr()) // Read client hello - clientHello, err := ReadBytes(lConn) + tmpBuffer := make([]byte, pxy.bufferSize) + clientHello, err := ReadBytes(lConn, tmpBuffer) if err != nil { log.Debug("[HTTPS] Error reading client hello from the client", err) return @@ -60,7 +61,7 @@ func (pxy *Proxy) handleHttps(lConn *net.TCPConn, exploit bool, initPkt *packet. // lConn.SetLinger(3) // rConn.SetLinger(3) - go Serve(rConn, lConn, "[HTTPS]", rConn.RemoteAddr().String(), initPkt.Domain(), pxy.timeout) + go Serve(rConn, lConn, "[HTTPS]", rConn.RemoteAddr().String(), initPkt.Domain(), pxy.timeout, pxy.bufferSize) if exploit { log.Debug("[HTTPS] Writing chunked client hello to ", initPkt.Domain()) @@ -77,7 +78,7 @@ func (pxy *Proxy) handleHttps(lConn *net.TCPConn, exploit bool, initPkt *packet. } } - Serve(lConn, rConn, "[HTTPS]", lConn.RemoteAddr().String(), initPkt.Domain(), pxy.timeout) + Serve(lConn, rConn, "[HTTPS]", lConn.RemoteAddr().String(), initPkt.Domain(), pxy.timeout, pxy.bufferSize) } func splitInChunks(bytes []byte, size int) [][]byte { diff --git a/proxy/io.go b/proxy/io.go index a8a1fff..f166bb3 100644 --- a/proxy/io.go +++ b/proxy/io.go @@ -9,8 +9,6 @@ import ( log "github.com/sirupsen/logrus" ) -const BUF_SIZE = 1024 - func WriteChunks(conn *net.TCPConn, c [][]byte) (n int, err error) { total := 0 for i := 0; i < len(c); i++ { @@ -25,45 +23,42 @@ func WriteChunks(conn *net.TCPConn, c [][]byte) (n int, err error) { return total, nil } -func ReadBytes(conn *net.TCPConn) ([]byte, error) { - ret := make([]byte, 0) - buf := make([]byte, BUF_SIZE) - - for { - n, err := conn.Read(buf) - if err != nil { - switch err.(type) { - case *net.OpError: - return nil, errors.New("timed out") - default: - return nil, err - } - } - ret = append(ret, buf[:n]...) - - if n < BUF_SIZE { - break - } - } - - if len(ret) == 0 { - return nil, io.EOF - } - - return ret, nil +func ReadBytes(conn *net.TCPConn, dest []byte) ([]byte, error) { + n, err := readBytesInternal(conn, dest) + return dest[:n], err } -func Serve(from *net.TCPConn, to *net.TCPConn, proto string, fd string, td string, timeout int) { - proto += " " - +func readBytesInternal(in io.Reader, dest []byte) (int, error) { + totalRead := 0 for { - if timeout > 0 { - from.SetReadDeadline( - time.Now().Add(time.Millisecond * time.Duration(timeout)), - ) - } + numRead, readErr := in.Read(dest[totalRead:]) + totalRead += numRead + if readErr != nil { + switch readErr.(type) { + case *net.OpError: + return totalRead, errors.New("timed out") + default: + return totalRead, readErr + } + } + if totalRead == 0 { + return 0, io.EOF + } + return totalRead, nil + } +} - buf, err := ReadBytes(from) +func Serve(from *net.TCPConn, to *net.TCPConn, proto string, fd string, td string, timeout int, bufferSize int) { + proto += " " + buf := make([]byte, bufferSize) + for { + if timeout > 0 { + from.SetReadDeadline( + time.Now().Add(time.Millisecond * time.Duration(timeout)), + ) + } + + bytesRead, err := ReadBytes(from, buf) if err != nil { if err == io.EOF { log.Debug(proto, "Finished ", fd) @@ -73,7 +68,7 @@ func Serve(from *net.TCPConn, to *net.TCPConn, proto string, fd string, td strin return } - if _, err := to.Write(buf); err != nil { + if _, err := to.Write(bytesRead); err != nil { log.Debug(proto, "Error Writing to ", td) return } diff --git a/proxy/proxy.go b/proxy/proxy.go index 24b16e6..d4301ae 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -20,6 +20,7 @@ type Proxy struct { resolver *dns.DnsResolver windowSize int allowedPattern []*regexp.Regexp + bufferSize int } func New(config *util.Config) *Proxy { @@ -30,6 +31,7 @@ func New(config *util.Config) *Proxy { windowSize: *config.WindowSize, allowedPattern: config.AllowedPattern, resolver: dns.NewResolver(config), + bufferSize: *config.BufferSize, } } @@ -46,7 +48,7 @@ func (pxy *Proxy) Start() { 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)) + log.Println("[PROXY] Number of white-listed pattern:", len(pxy.allowedPattern)) } for { @@ -57,7 +59,8 @@ func (pxy *Proxy) Start() { } go func() { - b, err := ReadBytes(conn.(*net.TCPConn)) + tmpBuf := make([]byte, pxy.bufferSize) + b, err := ReadBytes(conn.(*net.TCPConn), tmpBuf) if err != nil { return } @@ -77,8 +80,8 @@ func (pxy *Proxy) Start() { return } - matched := pxy.patternMatches([]byte(pkt.Domain())) - useSystemDns := !matched + matched := pxy.patternMatches([]byte(pkt.Domain())) + useSystemDns := !matched ip, err := pxy.resolver.Lookup(pkt.Domain(), useSystemDns) if err != nil { @@ -113,11 +116,11 @@ func (pxy *Proxy) patternMatches(bytes []byte) bool { for _, pattern := range pxy.allowedPattern { if pattern.Match(bytes) { - return true - } + return true + } } - return false + return false } func isLoopedRequest(ip net.IP) bool { diff --git a/util/config.go b/util/config.go index aca391c..2b5a9d1 100644 --- a/util/config.go +++ b/util/config.go @@ -22,6 +22,7 @@ type Config struct { AllowedPattern []*regexp.Regexp WindowSize *int Version *bool + BufferSize *int } type StringArray []string @@ -58,7 +59,7 @@ when not given, the client hello packet will be sent in two parts: fragmentation for the first data packet and the rest `) config.Version = flag.Bool("v", false, "print spoof-dpi's version; this may contain some other relevant information") - + config.BufferSize = flag.Int("buffer-size", 1024, "buffer size, in number of bytes, is the maximum amount of data that can be read at once from a remote resource") var allowedPattern StringArray flag.Var( &allowedPattern, From dbee4964f8eb655b9e54e0fb5ba6ac197ab7b7f8 Mon Sep 17 00:00:00 2001 From: Andrey Semenov Date: Fri, 9 Aug 2024 01:52:59 +0500 Subject: [PATCH 09/14] perf: remove loop --- proxy/io.go | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/proxy/io.go b/proxy/io.go index f166bb3..bb58c7e 100644 --- a/proxy/io.go +++ b/proxy/io.go @@ -28,24 +28,17 @@ func ReadBytes(conn *net.TCPConn, dest []byte) ([]byte, error) { return dest[:n], err } -func readBytesInternal(in io.Reader, dest []byte) (int, error) { - totalRead := 0 - for { - numRead, readErr := in.Read(dest[totalRead:]) - totalRead += numRead - if readErr != nil { - switch readErr.(type) { - case *net.OpError: - return totalRead, errors.New("timed out") - default: - return totalRead, readErr - } +func readBytesInternal(conn *net.TCPConn, dest []byte) (int, error) { + totalRead, err := conn.Read(dest) + if err != nil { + switch err.(type) { + case *net.OpError: + return totalRead, errors.New("timed out") + default: + return totalRead, err } - if totalRead == 0 { - return 0, io.EOF - } - return totalRead, nil } + return totalRead, nil } func Serve(from *net.TCPConn, to *net.TCPConn, proto string, fd string, td string, timeout int, bufferSize int) { From 173b5da2a59bfcb075286bb1819be5951dc6b021 Mon Sep 17 00:00:00 2001 From: Andrey Semenov Date: Fri, 9 Aug 2024 03:07:29 +0500 Subject: [PATCH 10/14] Parse request from connection instead of buffer --- packet/http.go | 21 ++++++++++++--------- proxy/proxy.go | 8 +++----- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/packet/http.go b/packet/http.go index 2e75260..5ca57f3 100644 --- a/packet/http.go +++ b/packet/http.go @@ -2,6 +2,7 @@ package packet import ( "bufio" + "io" "net" "net/http" "strings" @@ -56,12 +57,16 @@ func ParseUrl(raw []byte) { } -func NewHttpPacket(raw []byte) (*HttpPacket, error) { - pkt := &HttpPacket{raw: raw} - - pkt.parse() - - return pkt, nil +func NewHttpPacketFromReader(rdr io.Reader) (*HttpPacket, error) { + sb := strings.Builder{} + tee := io.TeeReader(rdr, &sb) + p := &HttpPacket{} + err := parse(p, bufio.NewReader(tee)) + if err != nil { + return nil, err + } + p.raw = []byte(sb.String()) + return p, nil } func (p *HttpPacket) Raw() []byte { @@ -123,8 +128,7 @@ func (p *HttpPacket) Tidy() { p.raw = []byte(result) } -func (p *HttpPacket) parse() error { - reader := bufio.NewReader(strings.NewReader(string(p.raw))) +func parse(p *HttpPacket, reader *bufio.Reader) error { request, err := http.ReadRequest(reader) if err != nil { return err @@ -152,6 +156,5 @@ func (p *HttpPacket) parse() error { } request.Body.Close() - return nil } diff --git a/proxy/proxy.go b/proxy/proxy.go index d4301ae..01c05fa 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -59,17 +59,15 @@ func (pxy *Proxy) Start() { } go func() { - tmpBuf := make([]byte, pxy.bufferSize) - b, err := ReadBytes(conn.(*net.TCPConn), tmpBuf) + pkt, err := packet.NewHttpPacketFromReader(conn) if err != nil { return } - log.Debug("[PROXY] Request from ", conn.RemoteAddr(), "\n\n", string(b)) + log.Debug("[PROXY] Request from ", conn.RemoteAddr(), "\n\n", string(pkt.Raw())) - pkt, err := packet.NewHttpPacket(b) if err != nil { - log.Debug("[PROXY] Error while parsing request: ", string(b)) + log.Debug("[PROXY] Error while parsing request: ", string(pkt.Raw())) conn.Close() return } From 874256c9bcf4a1314899707afc6a6fdaf524b83a Mon Sep 17 00:00:00 2001 From: Andrey Semenov Date: Sat, 10 Aug 2024 21:19:52 +0500 Subject: [PATCH 11/14] remove buffer-size parameter --- proxy/proxy.go | 4 +++- util/config.go | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/proxy/proxy.go b/proxy/proxy.go index 01c05fa..f1a7701 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -13,6 +13,8 @@ import ( "github.com/xvzc/SpoofDPI/util" ) +const BUFFER_SIZE = 1024 + type Proxy struct { addr string port int @@ -31,7 +33,7 @@ func New(config *util.Config) *Proxy { windowSize: *config.WindowSize, allowedPattern: config.AllowedPattern, resolver: dns.NewResolver(config), - bufferSize: *config.BufferSize, + bufferSize: BUFFER_SIZE, } } diff --git a/util/config.go b/util/config.go index 2b5a9d1..27ec576 100644 --- a/util/config.go +++ b/util/config.go @@ -22,7 +22,6 @@ type Config struct { AllowedPattern []*regexp.Regexp WindowSize *int Version *bool - BufferSize *int } type StringArray []string @@ -59,7 +58,6 @@ when not given, the client hello packet will be sent in two parts: fragmentation for the first data packet and the rest `) config.Version = flag.Bool("v", false, "print spoof-dpi's version; this may contain some other relevant information") - config.BufferSize = flag.Int("buffer-size", 1024, "buffer size, in number of bytes, is the maximum amount of data that can be read at once from a remote resource") var allowedPattern StringArray flag.Var( &allowedPattern, From 6b0547b4310b90cb1c31fc30250b380ec6245308 Mon Sep 17 00:00:00 2001 From: LiquidTheDangerous Date: Sun, 11 Aug 2024 16:44:34 +0500 Subject: [PATCH 12/14] fix: client hello --- proxy/https.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/https.go b/proxy/https.go index 634f671..359336c 100644 --- a/proxy/https.go +++ b/proxy/https.go @@ -45,7 +45,7 @@ func (pxy *Proxy) handleHttps(lConn *net.TCPConn, exploit bool, initPkt *packet. log.Debug("[HTTPS] Sent 200 Connection Estabalished to ", lConn.RemoteAddr()) // Read client hello - tmpBuffer := make([]byte, pxy.bufferSize) + tmpBuffer := make([]byte, 4096) clientHello, err := ReadBytes(lConn, tmpBuffer) if err != nil { log.Debug("[HTTPS] Error reading client hello from the client", err) From 852b789ac3f5b52288a2136102dea6408e2a55b7 Mon Sep 17 00:00:00 2001 From: LiquidTheDangerous Date: Sun, 11 Aug 2024 18:54:06 +0500 Subject: [PATCH 13/14] fix: client hello --- proxy/client_hello.go | 48 +++++++++++++++++++++++++++++++++++++++++++ proxy/https.go | 4 ++-- 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 proxy/client_hello.go diff --git a/proxy/client_hello.go b/proxy/client_hello.go new file mode 100644 index 0000000..102b7c8 --- /dev/null +++ b/proxy/client_hello.go @@ -0,0 +1,48 @@ +package proxy + +import ( + "encoding/binary" + "io" +) + +const headerLen = 5 + +type ClientHello struct { + Header ClientHelloHeader + Raw []byte //Header + Payload + RawHeader []byte + RawPayload []byte +} + +type ClientHelloHeader struct { + Type byte + ProtoVersion uint16 + PayloadLen uint16 +} + +func ReadClientHello(r io.Reader) (*ClientHello, error) { + var rawHeader [5]byte + _, err := io.ReadFull(r, rawHeader[:]) + if err != nil { + return nil, err + } + + header := ClientHelloHeader{ + Type: rawHeader[0], + ProtoVersion: binary.BigEndian.Uint16(rawHeader[1:3]), + PayloadLen: binary.BigEndian.Uint16(rawHeader[3:5]), + } + raw := make([]byte, header.PayloadLen+headerLen) + copy(raw[0:headerLen], rawHeader[:]) + _, err = io.ReadFull(r, raw[headerLen:]) + if err != nil { + return nil, err + } + hello := &ClientHello{ + Header: header, + Raw: raw, + } + hello.RawHeader = hello.Raw[:headerLen] + hello.RawPayload = hello.Raw[headerLen:] + return hello, nil +} diff --git a/proxy/https.go b/proxy/https.go index 359336c..1ef49f8 100644 --- a/proxy/https.go +++ b/proxy/https.go @@ -45,12 +45,12 @@ func (pxy *Proxy) handleHttps(lConn *net.TCPConn, exploit bool, initPkt *packet. log.Debug("[HTTPS] Sent 200 Connection Estabalished to ", lConn.RemoteAddr()) // Read client hello - tmpBuffer := make([]byte, 4096) - clientHello, err := ReadBytes(lConn, tmpBuffer) + hello, err := ReadClientHello(lConn) if err != nil { log.Debug("[HTTPS] Error reading client hello from the client", err) return } + clientHello := hello.Raw log.Debug("[HTTPS] Client sent hello ", len(clientHello), "bytes") From 48488a3af4395dbb3dd947c9bc214938d262bb65 Mon Sep 17 00:00:00 2001 From: LiquidTheDangerous Date: Wed, 14 Aug 2024 02:33:58 +0500 Subject: [PATCH 14/14] refactor --- proxy/client_hello.go | 44 ++++++++++++++++++++++++++++++------------- proxy/https.go | 6 +++--- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/proxy/client_hello.go b/proxy/client_hello.go index 102b7c8..09d1f57 100644 --- a/proxy/client_hello.go +++ b/proxy/client_hello.go @@ -7,28 +7,39 @@ import ( const headerLen = 5 -type ClientHello struct { - Header ClientHelloHeader +type TLSMessageType byte + +const ( + TLSInvalid TLSMessageType = 0x0 + TLSChangeCipherSpec TLSMessageType = 0x14 + TLSAlert TLSMessageType = 0x15 + TLSHandshake TLSMessageType = 0x16 + TLSApplicationData TLSMessageType = 0x17 + TLSHeartbeat TLSMessageType = 0x18 +) + +type TlsMessage struct { + Header TlsHeader Raw []byte //Header + Payload RawHeader []byte RawPayload []byte } -type ClientHelloHeader struct { - Type byte - ProtoVersion uint16 +type TlsHeader struct { + Type TLSMessageType + ProtoVersion uint16 // major | minor PayloadLen uint16 } -func ReadClientHello(r io.Reader) (*ClientHello, error) { +func ReadTlsMessage(r io.Reader) (*TlsMessage, error) { var rawHeader [5]byte _, err := io.ReadFull(r, rawHeader[:]) if err != nil { return nil, err } - header := ClientHelloHeader{ - Type: rawHeader[0], + header := TlsHeader{ + Type: TLSMessageType(rawHeader[0]), ProtoVersion: binary.BigEndian.Uint16(rawHeader[1:3]), PayloadLen: binary.BigEndian.Uint16(rawHeader[3:5]), } @@ -38,11 +49,18 @@ func ReadClientHello(r io.Reader) (*ClientHello, error) { if err != nil { return nil, err } - hello := &ClientHello{ - Header: header, - Raw: raw, + hello := &TlsMessage{ + Header: header, + Raw: raw, + RawHeader: raw[:headerLen], + RawPayload: raw[headerLen:], } - hello.RawHeader = hello.Raw[:headerLen] - hello.RawPayload = hello.Raw[headerLen:] return hello, nil } + +func IsClientHello(message *TlsMessage) bool { + // According to RFC 8446 section 4. + // first byte (Raw[5]) of handshake message should be 0x1 - means client_hello + return message.Header.Type == TLSHandshake && + message.Raw[5] == 0x1 +} diff --git a/proxy/https.go b/proxy/https.go index 1ef49f8..94eb1c4 100644 --- a/proxy/https.go +++ b/proxy/https.go @@ -45,12 +45,12 @@ func (pxy *Proxy) handleHttps(lConn *net.TCPConn, exploit bool, initPkt *packet. log.Debug("[HTTPS] Sent 200 Connection Estabalished to ", lConn.RemoteAddr()) // Read client hello - hello, err := ReadClientHello(lConn) - if err != nil { + m, err := ReadTlsMessage(lConn) + if err != nil || !IsClientHello(m) { log.Debug("[HTTPS] Error reading client hello from the client", err) return } - clientHello := hello.Raw + clientHello := m.Raw log.Debug("[HTTPS] Client sent hello ", len(clientHello), "bytes")