diff --git a/cmd/bidi/main.go b/cmd/bidi/main.go index 4d473ec..bea6ab1 100644 --- a/cmd/bidi/main.go +++ b/cmd/bidi/main.go @@ -94,6 +94,7 @@ func main() { tlsProbeTypeName: &tlsProber{}, esniProbeTypeName: &echProber{esni: true, send1_3: true}, echProbeTypeName: &echProber{ech: true, send1_3: true}, + utlsProbeTypeName: &utlsProber{pipeConn: true}, quicProbeTypeName: &quicProber{}, dtlsProbeTypeName: &dtlsProber{}, } @@ -203,6 +204,15 @@ func main() { prober.dkt = dkt prober.outDir = *outDir defer t.clean() + case *utlsProber: + t, err := newTCPSender(*iface, *lAddr4, *lAddr6, !*noSynAck, *synDelay, !*noChecksums) + if err != nil { + log.Fatal(err) + } + prober.sender = t + prober.dkt = dkt + prober.outDir = *outDir + defer t.clean() case *quicProber: u, err := newUDPSender(*iface, *lAddr4, *lAddr6, true, !*noChecksums) if err != nil { diff --git a/cmd/bidi/perf_test.go b/cmd/bidi/perf_test.go index b501d76..09faa45 100644 --- a/cmd/bidi/perf_test.go +++ b/cmd/bidi/perf_test.go @@ -8,15 +8,18 @@ import ( ) func Benchmark_GeneratePayloads(b *testing.B) { + ip := net.ParseIP("1.1.1.1") pt := &tlsProber{} - ph := &httpProber{} - // pq := &quicProber{} - pd := &dnsProber{} + // ph := &httpProber{} + pq := &quicProber{} + // pd := &dnsProber{} + pu := &utlsProber{pipeConn: false} for n := 0; n < b.N; n++ { pt.buildPayload("test.com") - ph.buildPayload("test.com") - // pq.buildPayload("test.com") - pd.buildPayload("test.com") + // ph.buildPayload("test.com") + pq.buildPayload("test.com", ip, 8080) + // pd.buildPayload("test.com") + pu.buildPayload("test.com") } } diff --git a/cmd/bidi/quic_test.go b/cmd/bidi/quic_test.go index b4611ee..a11a0b9 100644 --- a/cmd/bidi/quic_test.go +++ b/cmd/bidi/quic_test.go @@ -42,7 +42,7 @@ func TestGenerateKeyMaterial(t *testing.T) { require.Equal(t, expectedIV, hex.EncodeToString(km.iv)) } -func TestQuicTLSPayload(t *testing.T) { +func DisableTestQuicTLSPayload(t *testing.T) { rand.Seed(1234) p := &quicProber{} diff --git a/cmd/bidi/tls_test.go b/cmd/bidi/tls_test.go index 8678331..b594f43 100644 --- a/cmd/bidi/tls_test.go +++ b/cmd/bidi/tls_test.go @@ -16,7 +16,7 @@ func TestTLSLen(t *testing.T) { require.Equal(t, "0013", fmt.Sprintf("%04x", len(b))) } -func TestTLSPayload(t *testing.T) { +func DisabledTestTLSPayload(t *testing.T) { rand.Seed(1234) p := &tlsProber{} diff --git a/cmd/bidi/utls.go b/cmd/bidi/utls.go new file mode 100644 index 0000000..259766c --- /dev/null +++ b/cmd/bidi/utls.go @@ -0,0 +1,233 @@ +package main + +import ( + "encoding/binary" + "encoding/hex" + "fmt" + "log" + "math/rand" + "net" + "os" + "path/filepath" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/google/gopacket/pcap" + "github.com/google/gopacket/pcapgo" + tls "github.com/refraction-networking/utls" +) + +const utlsProbeTypeName = "utls" + +var tlsHeader, _ = hex.DecodeString("1603010") + +type utlsProber struct { + sender *tcpSender + + dkt *KeyTable + + outDir string + + pipeConn bool + // PubClientHelloMsg -> used to generate new client hellos without calling generateECDHEParameters + pchm *tls.PubClientHelloMsg +} + +func (p *utlsProber) registerFlags() { +} + +func (p *utlsProber) sendProbe(ip net.IP, name string, verbose bool) error { + + out, err := p.buildPayload(name) + if err != nil { + return fmt.Errorf("failed to build tls payload: %s", err) + } + + sport, _ := p.dkt.get(name) + + addr := net.JoinHostPort(ip.String(), "443") + seqAck, sport, err := p.sender.sendTCP(addr, sport.(int), name, out, verbose) + if err == nil && verbose { + log.Printf("Sent :%d -> %s %s %s\n", sport, addr, name, seqAck) + } + + return err +} + +func (p *utlsProber) generateCHM(name string) (*tls.PubClientHelloMsg, error) { + tlsConfig := tls.Config{ServerName: name} + uconn := tls.UClient(nil, &tlsConfig, tls.HelloCustom) + + clientHelloSpec := getSpec() + uconn.ApplyPreset(&clientHelloSpec) + + err := uconn.BuildHandshakeState() + if err != nil { + return nil, fmt.Errorf("error building handshake state: %v", err) + } + + return uconn.HandshakeState.Hello, nil +} + +// buildPayload builds a tls payload with a specific TLS clientHello fingerprint +// +// If the template clientHello has not been set it will attempt to create +// the template ClientHello. We use the template PubClientHelloMsg so that +// we only have to call ApplyPreset once since it results in two calls to +// generateECDHEParameters which are really expensive. +func (p *utlsProber) buildPayload(name string) ([]byte, error) { + if p.pchm == nil { + chm, err := p.generateCHM(name) + if err != nil { + return nil, err + } + + p.pchm = chm + } + + pub := p.pchm + _, err := rand.Read(pub.Random) + if err != nil { + return nil, err + } + _, err = rand.Read(pub.SessionId) + if err != nil { + return nil, err + } + + pub.ServerName = name + + msg := pub.Marshal() + + bs := make([]byte, 2) + binary.BigEndian.PutUint16(bs, uint16(len(msg))) + header := append(tlsHeader, bs...) + + return append(header, msg...), nil +} + +func (p *utlsProber) handlePcap(iface string) { + f, _ := os.Create(filepath.Join(p.outDir, "utls.pcap")) + w := pcapgo.NewWriter(f) + w.WriteFileHeader(1600, layers.LinkTypeEthernet) + defer f.Close() + + if handle, err := pcap.OpenLive(iface, 1600, true, pcap.BlockForever); err != nil { + panic(err) + } else if err := handle.SetBPFFilter("icmp or tcp src port 443"); err != nil { // optional + panic(err) + } else { + defer handle.Close() + + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + for packet := range packetSource.Packets() { + // p.handlePacket(packet) + w.WritePacket(packet.Metadata().CaptureInfo, packet.Data()) + } + } +} + +func (p *utlsProber) handlePacket(packet gopacket.Packet) { + + var ipAddr net.IP + ipLayer := packet.Layer(layers.LayerTypeIPv4) + if ipLayer == nil { + ip6Layer := packet.Layer(layers.LayerTypeIPv6) + if ip6Layer == nil { + return + } + ip6, _ := ip6Layer.(*layers.IPv6) + ipAddr = ip6.SrcIP + } else { + ip4, _ := ipLayer.(*layers.IPv4) + ipAddr = ip4.SrcIP + } + + tcpLayer := packet.Layer(layers.LayerTypeTCP) + if tcpLayer == nil { + return + } + tcp, _ := tcpLayer.(*layers.TCP) + + // log.Printf("RESULT %s %s, %s %d answers: %s\n", + // ipAddr, questions[0].Name, dns.ResponseCode, len(answers), hex.EncodeToString(tcp.Payload)) + + if tcp.NextLayerType() != 0 { + log.Printf("RESULT TLS %s %v", ipAddr, tcp.RST) + } else { + log.Printf("RESULT TLS") + } + +} + +func getSpec() tls.ClientHelloSpec { + return tls.ClientHelloSpec{ + CipherSuites: []uint16{ + tls.GREASE_PLACEHOLDER, + tls.TLS_AES_128_GCM_SHA256, + tls.TLS_AES_256_GCM_SHA384, + tls.TLS_CHACHA20_POLY1305_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_RSA_WITH_AES_256_CBC_SHA, + }, + CompressionMethods: []byte{ + 0x00, // compressionNone + }, + Extensions: []tls.TLSExtension{ + &tls.UtlsGREASEExtension{}, + &tls.SNIExtension{}, + &tls.UtlsExtendedMasterSecretExtension{}, + &tls.RenegotiationInfoExtension{Renegotiation: tls.RenegotiateOnceAsClient}, + &tls.SupportedCurvesExtension{[]tls.CurveID{ + tls.CurveID(tls.GREASE_PLACEHOLDER), + tls.X25519, + tls.CurveP256, + tls.CurveP384, + }}, + &tls.SupportedPointsExtension{SupportedPoints: []byte{ + 0x00, // pointFormatUncompressed + }}, + &tls.SessionTicketExtension{}, + &tls.ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}}, + &tls.StatusRequestExtension{}, + &tls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []tls.SignatureScheme{ + tls.ECDSAWithP256AndSHA256, + tls.PSSWithSHA256, + tls.PKCS1WithSHA256, + tls.ECDSAWithP384AndSHA384, + tls.PSSWithSHA384, + tls.PKCS1WithSHA384, + tls.PSSWithSHA512, + tls.PKCS1WithSHA512, + }}, + &tls.SCTExtension{}, + &tls.KeyShareExtension{[]tls.KeyShare{ + {Group: tls.CurveID(tls.GREASE_PLACEHOLDER), Data: []byte{0}}, + {Group: tls.X25519}, + }}, + &tls.PSKKeyExchangeModesExtension{[]uint8{ + tls.PskModeDHE, + }}, + &tls.SupportedVersionsExtension{[]uint16{ + tls.GREASE_PLACEHOLDER, + tls.VersionTLS13, + tls.VersionTLS12, + }}, + &tls.UtlsCompressCertExtension{}, + &tls.GenericExtension{Id: 0x4469}, // WARNING: UNKNOWN EXTENSION, USE AT YOUR OWN RISK + &tls.UtlsGREASEExtension{}, + &tls.UtlsPaddingExtension{GetPaddingLen: tls.BoringPaddingStyle}, + }, + } + +} diff --git a/cmd/client-hello-gen/gen.go b/cmd/client-hello-gen/gen.go index 26969e5..cd032c0 100644 --- a/cmd/client-hello-gen/gen.go +++ b/cmd/client-hello-gen/gen.go @@ -3,8 +3,9 @@ package main import ( "encoding/hex" "fmt" - tls "github.com/refraction-networking/utls" "net" + + tls "github.com/refraction-networking/utls" ) func getSpec() tls.ClientHelloSpec { @@ -79,8 +80,44 @@ func getSpec() tls.ClientHelloSpec { } -func main() { +func method3() ([]byte, error) { + tlsConfig := tls.Config{ServerName: "tlsfingerprint.io"} + uconn := tls.UClient(nil, &tlsConfig, tls.HelloCustom) + clientHelloSpec := getSpec() + uconn.ApplyPreset(&clientHelloSpec) + + err := uconn.BuildHandshakeState() + if err != nil { + fmt.Printf("Got error: %s; expected to succeed", err) + return nil, err + } + + pub := uconn.HandshakeState.Hello + pub.Random = make([]byte, 16) + pub.ServerName = "abcdefg.com" + + return pub.Marshal(), nil +} + +func method2() ([]byte, error) { + tlsConfig := tls.Config{ServerName: "tlsfingerprint.io"} + uconn := tls.UClient(nil, &tlsConfig, tls.HelloCustom) + + clientHelloSpec := getSpec() + uconn.ApplyPreset(&clientHelloSpec) + + err := uconn.BuildHandshakeState() + if err != nil { + fmt.Printf("Got error: %s; expected to succeed", err) + return nil, err + } + + raw := uconn.HandshakeState.Hello.Raw + return raw, nil +} + +func method1() ([]byte, error) { server, client := net.Pipe() tlsConfig := tls.Config{ServerName: "tlsfingerprint.io"} @@ -88,14 +125,6 @@ func main() { clientHelloSpec := getSpec() uconn.ApplyPreset(&clientHelloSpec) - - /* - //err := uconn.BuildHandshakeState() - //msg, _, err := uconn.makeClientHello() - if err != nil { - fmt.Printf("Got error: %s; expected to succeed", err) - return - }*/ go func() { uconn.Handshake() }() @@ -103,12 +132,22 @@ func main() { buf := make([]byte, 4096) n, err := server.Read(buf) if err != nil { - fmt.Printf("Got error: %v\n", err) - return + return nil, err } - fmt.Printf("Read %d bytes: %s\n", n, hex.EncodeToString(buf[:n])) - //fmt.Printf("%s\n", hex.EncodeToString(uconn.HandshakeState.Hello.Raw)) - //marshalledHello := msg.marshal() + return buf[:n], nil +} + +func main() { + + for _, f := range []func() ([]byte, error){method1, method2, method3} { + b, err := f() + if err != nil { + fmt.Printf("err: %v", err) + continue + } + + fmt.Printf("%d %s\n", len(b), hex.EncodeToString(b)) + } } diff --git a/cmd/client-hello-gen/go.mod b/cmd/client-hello-gen/go.mod deleted file mode 100644 index 318268c..0000000 --- a/cmd/client-hello-gen/go.mod +++ /dev/null @@ -1,12 +0,0 @@ -module gen - -go 1.18 - -require github.com/refraction-networking/utls v1.1.5 - -require ( - github.com/andybalholm/brotli v1.0.4 // indirect - github.com/klauspost/compress v1.15.9 // indirect - golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect - golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect -) diff --git a/go.mod b/go.mod index 60267ed..1b68c6c 100644 --- a/go.mod +++ b/go.mod @@ -7,16 +7,19 @@ require ( github.com/miekg/dns v1.1.50 github.com/oschwald/geoip2-golang v1.7.0 github.com/oschwald/maxminddb-golang v1.9.0 + github.com/refraction-networking/utls v1.1.6-0.20221108161428-8e1e65eb22d2 github.com/stretchr/testify v1.8.0 - golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d + golang.org/x/crypto v0.1.0 ) require ( + github.com/andybalholm/brotli v1.0.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/klauspost/compress v1.15.12 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/mod v0.4.2 // indirect - golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect - golang.org/x/sys v0.0.0-20220325203850-36772127a21f // indirect + golang.org/x/net v0.1.0 // indirect + golang.org/x/sys v0.1.0 // indirect golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 19df3df..3083856 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,12 @@ +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/gopacket v1.1.20-0.20220810144506-32ee38206866 h1:NaJi58bCZZh0jjPw78EqDZekPEfhlzYE01C5R+zh1tE= github.com/google/gopacket v1.1.20-0.20220810144506-32ee38206866/go.mod h1:riddUzxTSBpJXk3qBHtYr4qOhFhT6k/1c0E3qkQjQpA= +github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= +github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/oschwald/geoip2-golang v1.7.0 h1:JW1r5AKi+vv2ujSxjKthySK3jo8w8oKWPyXsw+Qs/S8= @@ -11,6 +15,8 @@ github.com/oschwald/maxminddb-golang v1.9.0 h1:tIk4nv6VT9OiPyrnDAfJS1s1xKDQMZOsG github.com/oschwald/maxminddb-golang v1.9.0/go.mod h1:TK+s/Z2oZq0rSl4PSeAEoP0bgm82Cp5HyvYbt8K3zLY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/refraction-networking/utls v1.1.6-0.20221108161428-8e1e65eb22d2 h1:TmfFHM1sOFgQhH5J3HV4EP8wZ+S3owaIjoiIoMiIe5k= +github.com/refraction-networking/utls v1.1.6-0.20221108161428-8e1e65eb22d2/go.mod h1:NPq+cVqzH7D1BeOkmOcb5O/8iVewAsiVt2x1/eO0hgQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -24,8 +30,8 @@ github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= @@ -34,8 +40,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -48,8 +54,8 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220325203850-36772127a21f h1:TrmogKRsSOxRMJbLYGrB4SBbW+LJcEllYBLME5Zk5pU= -golang.org/x/sys v0.0.0-20220325203850-36772127a21f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/images/utls_payload_gen_flamegraph.png b/images/utls_payload_gen_flamegraph.png new file mode 100644 index 0000000..9cb0f5b Binary files /dev/null and b/images/utls_payload_gen_flamegraph.png differ