mic.git


mic

~ proxy for JA4-TLS fingerprinting evasion
git clone https://github.com/djnnvx/mic.git
log | files | refs | archive | README | LICENSE

commit 96d1caed0f9a85f50a81e21b7802cfe5db513afc
parent 0e039eed56aa25e9fb48235cfbe27188f86a60a1
Author: djnn <[email protected]>
Date: Wed, 18 Mar 2026 18:24:14 +0900

feat(meta): swap out utls libraries to be more up-to-date

Diffstat:
Mcmd/probe/main.go | 21+++++++++++++++++++--
Mcmd/root.go | 2+-
Mfingerprint/fingerprint.go | 2+-
Mfingerprint/tls.go | 16+++++++---------
Mfingerprint/tls_test.go | 8++++----
Mgo.mod | 8++++----
Mgo.sum | 14++++++++------
Mproxy/proxy.go | 4++--
Mproxy/proxy_test.go | 2+-
Mtest/client/main.go | 4++--

10 files changed, 49 insertions(+), 32 deletions(-)

diff --git a/cmd/probe/main.go b/cmd/probe/main.go
@@ -11,7 +11,7 @@ import (
 	"net"
 	"net/http"
 
-	utls "github.com/refraction-networking/utls"
+	utls "github.com/bogdanfinn/utls"
 	"golang.org/x/net/http2"
 )
 
@@ -19,11 +19,28 @@ var presets = []struct {
 	name string
 	id   utls.ClientHelloID
 }{
+	// Chrome
 	{"HelloChrome_120", utls.HelloChrome_120},
 	{"HelloChrome_120_PQ", utls.HelloChrome_120_PQ},
+	{"HelloChrome_131", utls.HelloChrome_131},
+	{"HelloChrome_133", utls.HelloChrome_133},
+	// Firefox
 	{"HelloFirefox_120", utls.HelloFirefox_120},
+	// Safari / iOS
+	{"HelloSafari_15_6_1", utls.HelloSafari_15_6_1},
 	{"HelloSafari_16_0", utls.HelloSafari_16_0},
+	{"HelloIOS_15_5", utls.HelloIOS_15_5},
+	{"HelloIOS_15_6", utls.HelloIOS_15_6},
+	{"HelloIOS_16_0", utls.HelloIOS_16_0},
+	// Edge
+	{"HelloEdge_85", utls.HelloEdge_85},
 	{"HelloEdge_106", utls.HelloEdge_106},
+	// Opera
+	{"HelloOpera_89", utls.HelloOpera_89},
+	{"HelloOpera_90", utls.HelloOpera_90},
+	{"HelloOpera_91", utls.HelloOpera_91},
+	// Android
+	{"HelloAndroid_11_OkHttp", utls.HelloAndroid_11_OkHttp},
 }
 
 func dialUTLS(ctx context.Context, id utls.ClientHelloID) (*utls.UConn, error) {
@@ -31,7 +48,7 @@ func dialUTLS(ctx context.Context, id utls.ClientHelloID) (*utls.UConn, error) {
 	if err != nil {
 		return nil, err
 	}
-	uconn := utls.UClient(tcpConn, &utls.Config{ServerName: "tlsinfo.me"}, id)
+	uconn := utls.UClient(tcpConn, &utls.Config{ServerName: "tlsinfo.me"}, id, false, false, false)
 	if err := uconn.Handshake(); err != nil {
 		tcpConn.Close()
 		return nil, err
diff --git a/cmd/root.go b/cmd/root.go
@@ -24,7 +24,7 @@ var SKID_ASCII_ART = `
       --------  ⠀⠀⢸⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣀⣀⣀⣙⢿⣿⣿⣿⣿⣿⣿⣦⡀⠀⠀⠀⠀⠀
         ⠀⠀⠀⠀  ⠀⠀⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣶⣶⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠻⣿⣿⣿⣿⣿⣿⣿⣄⠀⠀⠀⠀
         djnn.sh⠀⠀⠀⠀⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠀⠀⢹⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀
-        ⠀v0.0.1  ⠀⠀⢠⣿⣿⣿⣿⡟⠹⠿⠟⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠀⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀
+        ⠀v0.0.3  ⠀⠀⢠⣿⣿⣿⣿⡟⠹⠿⠟⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠀⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀
         ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡿⠋⡬⢿⣿⣷⣤⣤⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀⠀⠀⠀⠀⠸⣿⣿⣿⣿⣿⣿⣿⣿⡀⠀
         ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⡇⢸⡇⢸⣿⣿⣿⠟⠁⢀⣬⢽⣿⣿⣿⣿⣿⣿⠋⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀
         ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣧⣈⣛⣿⣿⣿⡇⠀⠀⣾⠁⢀⢻⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀
diff --git a/fingerprint/fingerprint.go b/fingerprint/fingerprint.go
@@ -2,7 +2,7 @@
 // outbound connection fingerprints (TLS ClientHello, TCP socket options, etc.).
 package fingerprint
 
-import utls "github.com/refraction-networking/utls"
+import utls "github.com/bogdanfinn/utls"
 
 // Applier is the base interface for all fingerprint types.
 type Applier interface {
diff --git a/fingerprint/tls.go b/fingerprint/tls.go
@@ -3,24 +3,22 @@ package fingerprint
 import (
 	"fmt"
 
-	utls "github.com/refraction-networking/utls"
+	utls "github.com/bogdanfinn/utls"
 )
 
 // Table maps known JA4-TLS fingerprint hashes to utls ClientHelloID presets.
-// JA4 hashes are computed from real traffic captures; verify against current
-// browser versions when adding new entries.
-// Table maps JA4-TLS hashes to utls ClientHelloID presets.
 //
 // Hashes are measured empirically against tlsinfo.me using cmd/probe — they
 // reflect what the utls preset actually emits, not captures from real browsers.
 // Re-run cmd/probe after upgrading the utls dependency to verify they still match.
 //
-// Note: HelloChrome_120_PQ produces the same JA4 as HelloChrome_120 because the
-// post-quantum key share (X25519MLKEM768) falls in a key_share extension entry
-// that JA4 does not distinguish. Use HelloChrome_120_PQ directly in dialTarget
-// if you specifically need the PQ key exchange behaviour.
+// Note: JA4 does not distinguish key_share entries, so Chrome_120, Chrome_120_PQ,
+// and Chrome_131 all produce the same hash. Use the preset directly in dialTarget
+// if you need a specific variant (e.g. HelloChrome_120_PQ for PQ key exchange).
+// Similarly, Safari_15_6_1, Safari_16_0, and all iOS variants share one hash.
 var Table = map[string]utls.ClientHelloID{
-	"t13d1516h2_8daaf6152771_02713d6af862": utls.HelloChrome_120,
+	"t13d1516h2_8daaf6152771_02713d6af862": utls.HelloChrome_131,
+	"t13d1516h2_8daaf6152771_d8a2da3f94cd": utls.HelloChrome_133,
 	"t13d1715h2_5b57614c22b0_5c2c66f702b0": utls.HelloFirefox_120,
 	"t13d2014h2_a09f3c656075_14788d8d241b": utls.HelloSafari_16_0,
 	"t13d1516h2_8daaf6152771_e5627efa2ab1": utls.HelloEdge_106,
diff --git a/fingerprint/tls_test.go b/fingerprint/tls_test.go
@@ -4,19 +4,19 @@ import (
 	"strings"
 	"testing"
 
+	utls "github.com/bogdanfinn/utls"
 	"github.com/djnnvx/mic/fingerprint"
-	utls "github.com/refraction-networking/utls"
 )
 
-const knownJA4 = "t13d1516h2_8daaf6152771_b0da82dd1658"
+const knownJA4 = "t13d1516h2_8daaf6152771_02713d6af862"
 
 func TestLookup_Known(t *testing.T) {
 	id, ok := fingerprint.Lookup(knownJA4)
 	if !ok {
 		t.Fatalf("Lookup(%q) returned false; expected true", knownJA4)
 	}
-	if id != utls.HelloChrome_120 {
-		t.Errorf("Lookup(%q) = %v; want HelloChrome_120", knownJA4, id)
+	if id.Str() != utls.HelloChrome_131.Str() {
+		t.Errorf("Lookup(%q) = %v; want HelloChrome_131", knownJA4, id)
 	}
 }
 
diff --git a/go.mod b/go.mod
@@ -3,18 +3,18 @@ module github.com/djnnvx/mic
 go 1.24.4
 
 require (
+	github.com/bogdanfinn/utls v1.7.7-barnius
 	github.com/pelletier/go-toml/v2 v2.2.4
-	github.com/refraction-networking/utls v1.8.0
 	github.com/spf13/cobra v1.9.1
+	golang.org/x/net v0.38.0
 )
 
 require (
-	github.com/andybalholm/brotli v1.0.6 // indirect
+	github.com/andybalholm/brotli v1.1.1 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
-	github.com/klauspost/compress v1.17.4 // indirect
+	github.com/klauspost/compress v1.17.11 // indirect
 	github.com/spf13/pflag v1.0.6 // indirect
 	golang.org/x/crypto v0.36.0 // indirect
-	golang.org/x/net v0.38.0 // indirect
 	golang.org/x/sys v0.31.0 // indirect
 	golang.org/x/text v0.23.0 // indirect
 )
diff --git a/go.sum b/go.sum
@@ -1,19 +1,21 @@
-github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
-github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
+github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
+github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
+github.com/bogdanfinn/utls v1.7.7-barnius h1:OuJ497cc7F3yKNVHRsYPQdGggmk5x6+V5ZlrCR7fOLU=
+github.com/bogdanfinn/utls v1.7.7-barnius/go.mod h1:aAK1VZQlpKZClF1WEQeq6kyclbkPq4hz6xTbB5xSlmg=
 github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
-github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
-github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
+github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
+github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
 github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
-github.com/refraction-networking/utls v1.8.0 h1:L38krhiTAyj9EeiQQa2sg+hYb4qwLCqdMcpZrRfbONE=
-github.com/refraction-networking/utls v1.8.0/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
 github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
 github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
 github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
+github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
 golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
 golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
 golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
diff --git a/proxy/proxy.go b/proxy/proxy.go
@@ -8,8 +8,8 @@ import (
 	"strings"
 	"sync"
 
+	utls "github.com/bogdanfinn/utls"
 	"github.com/djnnvx/mic/fingerprint"
-	utls "github.com/refraction-networking/utls"
 )
 
 type Proxy struct {
@@ -56,7 +56,7 @@ func (p *Proxy) dialTarget(host string) (*utls.UConn, error) {
 		cfg.RootCAs = p.CAPool
 	}
 
-	uconn := utls.UClient(tcpConn, cfg, helloID)
+	uconn := utls.UClient(tcpConn, cfg, helloID, false, false, false)
 	if err := uconn.Handshake(); err != nil {
 		uconn.Close()
 		return nil, err
diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go
@@ -77,7 +77,7 @@ func TestDialTarget_HandshakeSucceeds(t *testing.T) {
 		io.Copy(io.Discard, conn)
 	}()
 
-	fp, err := fingerprint.NewTLS("t13d1516h2_8daaf6152771_b0da82dd1658")
+	fp, err := fingerprint.NewTLS("t13d1516h2_8daaf6152771_02713d6af862")
 	if err != nil {
 		t.Fatalf("fingerprint.NewTLS: %v", err)
 	}
diff --git a/test/client/main.go b/test/client/main.go
@@ -7,7 +7,7 @@ import (
 	"net/http"
 	"strings"
 
-	utls "github.com/refraction-networking/utls"
+	utls "github.com/bogdanfinn/utls"
 )
 
 func main() {
@@ -42,7 +42,7 @@ func main() {
 	// 4. Create a uTLS client from the proxied connection.
 	// We'll use the Chrome 116 fingerprint for the client.
 	utlsConfig := &utls.Config{ServerName: strings.Split(targetAddr, ":")[0]}
-	uclient := utls.UClient(proxyConn, utlsConfig, utls.HelloRandomized)
+	uclient := utls.UClient(proxyConn, utlsConfig, utls.HelloRandomized, false, false, false)
 
 	// 5. Perform the TLS handshake and get the handshake state.
 	if err := uclient.Handshake(); err != nil {