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/client_hello.go b/proxy/client_hello.go new file mode 100644 index 0000000..09d1f57 --- /dev/null +++ b/proxy/client_hello.go @@ -0,0 +1,66 @@ +package proxy + +import ( + "encoding/binary" + "io" +) + +const headerLen = 5 + +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 TlsHeader struct { + Type TLSMessageType + ProtoVersion uint16 // major | minor + PayloadLen uint16 +} + +func ReadTlsMessage(r io.Reader) (*TlsMessage, error) { + var rawHeader [5]byte + _, err := io.ReadFull(r, rawHeader[:]) + if err != nil { + return nil, err + } + + header := TlsHeader{ + Type: TLSMessageType(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 := &TlsMessage{ + Header: header, + Raw: raw, + RawHeader: raw[:headerLen], + RawPayload: 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/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..94eb1c4 100644 --- a/proxy/https.go +++ b/proxy/https.go @@ -45,11 +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 - clientHello, err := ReadBytes(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 := m.Raw log.Debug("[HTTPS] Client sent hello ", len(clientHello), "bytes") @@ -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..bb58c7e 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,35 @@ 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) { +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 + } + } + return totalRead, nil +} + +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)), - ) - } + if timeout > 0 { + from.SetReadDeadline( + time.Now().Add(time.Millisecond * time.Duration(timeout)), + ) + } - buf, err := ReadBytes(from) + bytesRead, err := ReadBytes(from, buf) if err != nil { if err == io.EOF { log.Debug(proto, "Finished ", fd) @@ -73,7 +61,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..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 @@ -20,6 +22,7 @@ type Proxy struct { resolver *dns.DnsResolver windowSize int allowedPattern []*regexp.Regexp + bufferSize int } func New(config *util.Config) *Proxy { @@ -30,6 +33,7 @@ func New(config *util.Config) *Proxy { windowSize: *config.WindowSize, allowedPattern: config.AllowedPattern, resolver: dns.NewResolver(config), + bufferSize: BUFFER_SIZE, } } @@ -46,7 +50,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,16 +61,15 @@ func (pxy *Proxy) Start() { } go func() { - b, err := ReadBytes(conn.(*net.TCPConn)) + 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 } @@ -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..27ec576 100644 --- a/util/config.go +++ b/util/config.go @@ -58,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") - var allowedPattern StringArray flag.Var( &allowedPattern,