mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-07-31 07:57:22 +02:00
288 lines
12 KiB
Go
288 lines
12 KiB
Go
package surf
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"math/rand"
|
|
"net"
|
|
|
|
"github.com/enetx/g"
|
|
"github.com/enetx/http"
|
|
"github.com/enetx/surf/internal/specclone"
|
|
"github.com/enetx/surf/pkg/connectproxy"
|
|
"github.com/enetx/surf/profiles/chrome"
|
|
"github.com/enetx/surf/profiles/firefox"
|
|
|
|
utls "github.com/enetx/utls"
|
|
)
|
|
|
|
// JA provides JA3/4 TLS fingerprinting capabilities for HTTP clients.
|
|
// JA is a method for creating SSL/TLS client fingerprints to identify and classify malware
|
|
// or other applications. This struct allows configuring various TLS ClientHello specifications
|
|
// to mimic different browsers and applications for advanced HTTP client behavior.
|
|
//
|
|
// Reference: https://lwthiker.com/networks/2022/06/17/tls-fingerprinting.html
|
|
type JA struct {
|
|
spec utls.ClientHelloSpec // Custom TLS ClientHello specification
|
|
id utls.ClientHelloID // Predefined TLS ClientHello identifier
|
|
builder *Builder // Reference to the parent builder for method chaining
|
|
}
|
|
|
|
// SetHelloID sets a ClientHelloID for the TLS connection.
|
|
//
|
|
// The provided ClientHelloID is used to customize the TLS handshake. This
|
|
// should be a valid identifier that can be mapped to a specific ClientHelloSpec.
|
|
//
|
|
// It returns a pointer to the Options struct for method chaining. This allows
|
|
// additional configuration methods to be called on the result.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// JA().SetHelloID(utls.HelloChrome_Auto)
|
|
func (j *JA) SetHelloID(id utls.ClientHelloID) *Builder {
|
|
j.id = id
|
|
return j.build()
|
|
}
|
|
|
|
// SetHelloSpec sets a custom ClientHelloSpec for the TLS connection.
|
|
//
|
|
// This method allows you to set a custom ClientHelloSpec to be used during the TLS handshake.
|
|
// The provided spec should be a valid ClientHelloSpec.
|
|
//
|
|
// It returns a pointer to the Options struct for method chaining. This allows
|
|
// additional configuration methods to be called on the result.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// JA().SetHelloSpec(spec)
|
|
func (j *JA) SetHelloSpec(spec utls.ClientHelloSpec) *Builder {
|
|
j.spec = spec
|
|
return j.build()
|
|
}
|
|
|
|
// build applies JA3/4 TLS fingerprinting configuration to the HTTP client.
|
|
// This method configures the client with custom TLS settings and proxy support for JA3/4 fingerprinting.
|
|
//
|
|
// The method performs several key operations:
|
|
// 1. Skips configuration if HTTP/3 is being used (JA3/4 only works with HTTP/1.1 and HTTP/2)
|
|
// 2. Adds connection cleanup middleware if not using singleton pattern
|
|
// 3. Configures proxy settings for both static and dynamic proxy configurations
|
|
// 4. Wraps the transport with a custom round tripper that implements JA3/4 fingerprinting
|
|
//
|
|
// Returns the builder instance for method chaining.
|
|
func (j *JA) build() *Builder {
|
|
return j.builder.addCliMW(func(c *Client) error {
|
|
// JA3 fingerprinting is not compatible with HTTP/3 - skip if HTTP/3 is used
|
|
if _, ok := c.GetTransport().(*uquicTransport); ok {
|
|
return nil
|
|
}
|
|
|
|
if !j.builder.singleton {
|
|
j.builder.addRespMW(closeIdleConnectionsMW, 0)
|
|
}
|
|
|
|
if j.builder.proxy != nil {
|
|
var proxy string
|
|
|
|
// Handle static proxy configurations
|
|
switch v := j.builder.proxy.(type) {
|
|
case string:
|
|
proxy = v
|
|
case g.String:
|
|
proxy = v.Std()
|
|
}
|
|
|
|
if proxy != "" {
|
|
// Static proxy configuration
|
|
dialer, err := connectproxy.NewDialer(proxy)
|
|
if err != nil {
|
|
c.GetTransport().(*http.Transport).DialContext = func(context.Context, string, string) (net.Conn, error) {
|
|
return nil, fmt.Errorf("proxy dialer init failed: %w", err)
|
|
}
|
|
} else {
|
|
c.GetTransport().(*http.Transport).DialContext = dialer.DialContext
|
|
}
|
|
} else {
|
|
// Dynamic proxy configuration - evaluate proxy per connection
|
|
c.GetTransport().(*http.Transport).DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
var proxy string
|
|
|
|
switch v := j.builder.proxy.(type) {
|
|
case func() g.String:
|
|
proxy = v().Std()
|
|
case []string:
|
|
if len(v) > 0 {
|
|
proxy = v[rand.Intn(len(v))]
|
|
}
|
|
case g.Slice[string]:
|
|
proxy = v.Random()
|
|
case g.Slice[g.String]:
|
|
proxy = v.Random().Std()
|
|
}
|
|
|
|
if proxy == "" {
|
|
return c.GetDialer().DialContext(ctx, network, addr)
|
|
}
|
|
|
|
dialer, err := connectproxy.NewDialer(proxy)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create proxy dialer for %s: %w", proxy, err)
|
|
}
|
|
|
|
return dialer.DialContext(ctx, network, addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Wrap the transport with JA3/4 fingerprinting round tripper
|
|
c.GetClient().Transport = newRoundTripper(j, c.GetTransport())
|
|
|
|
return nil
|
|
}, math.MaxInt)
|
|
}
|
|
|
|
// getSpec determines the ClientHelloSpec to be used for the TLS connection.
|
|
//
|
|
// The ClientHelloSpec is selected based on the following order of precedence:
|
|
// 1. If a custom ClientHelloID is set (via SetHelloID), it attempts to convert this ID to a ClientHelloSpec.
|
|
// 2. If none of the above conditions are met, it returns the currently set ClientHelloSpec.
|
|
//
|
|
// This method returns the selected ClientHelloSpec along with an error value. If an error occurs
|
|
// during conversion, it returns the error.
|
|
func (j *JA) getSpec() g.Result[utls.ClientHelloSpec] {
|
|
if !j.id.IsSet() {
|
|
return g.ResultOf(utls.UTLSIdToSpec(j.id))
|
|
}
|
|
|
|
spec := specclone.Clone(&j.spec)
|
|
return g.Ok(*spec)
|
|
}
|
|
|
|
// Browser and application fingerprinting methods.
|
|
// These methods provide convenient shortcuts to mimic various popular browsers and applications
|
|
// by setting predefined ClientHelloID values that match their TLS fingerprints.
|
|
|
|
// Android sets the JA3/4 fingerprint to mimic Android 11 OkHttp client.
|
|
func (j *JA) Android() *Builder { return j.SetHelloID(utls.HelloAndroid_11_OkHttp) }
|
|
|
|
// Chrome sets the JA3/4 fingerprint to mimic the latest Chrome browser (auto-detection).
|
|
func (j *JA) Chrome() *Builder { return j.SetHelloID(utls.HelloChrome_Auto) }
|
|
|
|
// Chrome58 sets the JA3/4 fingerprint to mimic Chrome version 58.
|
|
func (j *JA) Chrome58() *Builder { return j.SetHelloID(utls.HelloChrome_58) }
|
|
|
|
// Chrome62 sets the JA3/4 fingerprint to mimic Chrome version 62.
|
|
func (j *JA) Chrome62() *Builder { return j.SetHelloID(utls.HelloChrome_62) }
|
|
|
|
// Chrome70 sets the JA3/4 fingerprint to mimic Chrome version 70.
|
|
func (j *JA) Chrome70() *Builder { return j.SetHelloID(utls.HelloChrome_70) }
|
|
|
|
// Chrome72 sets the JA3/4 fingerprint to mimic Chrome version 72.
|
|
func (j *JA) Chrome72() *Builder { return j.SetHelloID(utls.HelloChrome_72) }
|
|
|
|
// Chrome83 sets the JA3/4 fingerprint to mimic Chrome version 83.
|
|
func (j *JA) Chrome83() *Builder { return j.SetHelloID(utls.HelloChrome_83) }
|
|
|
|
// Chrome87 sets the JA3/4 fingerprint to mimic Chrome version 87.
|
|
func (j *JA) Chrome87() *Builder { return j.SetHelloID(utls.HelloChrome_87) }
|
|
|
|
// Chrome96 sets the JA3/4 fingerprint to mimic Chrome version 96.
|
|
func (j *JA) Chrome96() *Builder { return j.SetHelloID(utls.HelloChrome_96) }
|
|
|
|
// Chrome100 sets the JA3/4 fingerprint to mimic Chrome version 100.
|
|
func (j *JA) Chrome100() *Builder { return j.SetHelloID(utls.HelloChrome_100) }
|
|
|
|
// Chrome102 sets the JA3/4 fingerprint to mimic Chrome version 102.
|
|
func (j *JA) Chrome102() *Builder { return j.SetHelloID(utls.HelloChrome_102) }
|
|
|
|
// Chrome106 sets the JA3/4 fingerprint to mimic Chrome version 106 with shuffled extensions.
|
|
func (j *JA) Chrome106() *Builder { return j.SetHelloID(utls.HelloChrome_106_Shuffle) }
|
|
|
|
// Chrome120 sets the JA3/4 fingerprint to mimic Chrome version 120.
|
|
func (j *JA) Chrome120() *Builder { return j.SetHelloID(utls.HelloChrome_120) }
|
|
|
|
// Chrome120PQ sets the JA3/4 fingerprint to mimic Chrome version 120 with post-quantum cryptography support.
|
|
func (j *JA) Chrome120PQ() *Builder { return j.SetHelloID(utls.HelloChrome_120_PQ) }
|
|
|
|
// Chrome142 sets the JA3/4 fingerprint to mimic Chrome version 142.
|
|
func (j *JA) Chrome142() *Builder { return j.SetHelloSpec(chrome.HelloChrome_142) }
|
|
|
|
// Edge sets the JA3/4 fingerprint to mimic Microsoft Edge version 85.
|
|
func (j *JA) Edge() *Builder { return j.SetHelloID(utls.HelloEdge_85) }
|
|
|
|
// Edge85 sets the JA3/4 fingerprint to mimic Microsoft Edge version 85.
|
|
func (j *JA) Edge85() *Builder { return j.SetHelloID(utls.HelloEdge_85) }
|
|
|
|
// Edge106 sets the JA3/4 fingerprint to mimic Microsoft Edge version 106.
|
|
func (j *JA) Edge106() *Builder { return j.SetHelloID(utls.HelloEdge_106) }
|
|
|
|
// Firefox sets the JA3/4 fingerprint to mimic the latest Firefox browser (auto-detection).
|
|
func (j *JA) Firefox() *Builder { return j.SetHelloID(utls.HelloFirefox_Auto) }
|
|
|
|
// Firefox55 sets the JA3/4 fingerprint to mimic Firefox version 55.
|
|
func (j *JA) Firefox55() *Builder { return j.SetHelloID(utls.HelloFirefox_55) }
|
|
|
|
// Firefox56 sets the JA3/4 fingerprint to mimic Firefox version 56.
|
|
func (j *JA) Firefox56() *Builder { return j.SetHelloID(utls.HelloFirefox_56) }
|
|
|
|
// Firefox63 sets the JA3/4 fingerprint to mimic Firefox version 63.
|
|
func (j *JA) Firefox63() *Builder { return j.SetHelloID(utls.HelloFirefox_63) }
|
|
|
|
// Firefox65 sets the JA3/4 fingerprint to mimic Firefox version 65.
|
|
func (j *JA) Firefox65() *Builder { return j.SetHelloID(utls.HelloFirefox_65) }
|
|
|
|
// Firefox99 sets the JA3/4 fingerprint to mimic Firefox version 99.
|
|
func (j *JA) Firefox99() *Builder { return j.SetHelloID(utls.HelloFirefox_99) }
|
|
|
|
// Firefox102 sets the JA3/4 fingerprint to mimic Firefox version 102.
|
|
func (j *JA) Firefox102() *Builder { return j.SetHelloID(utls.HelloFirefox_102) }
|
|
|
|
// Firefox105 sets the JA3/4 fingerprint to mimic Firefox version 105.
|
|
func (j *JA) Firefox105() *Builder { return j.SetHelloID(utls.HelloFirefox_105) }
|
|
|
|
// Firefox120 sets the JA3/4 fingerprint to mimic Firefox version 120.
|
|
func (j *JA) Firefox120() *Builder { return j.SetHelloID(utls.HelloFirefox_120) }
|
|
|
|
// Firefox141 sets the JA3/4 fingerprint to mimic Firefox version 141.
|
|
func (j *JA) Firefox141() *Builder { return j.SetHelloID(utls.HelloFirefox_141) }
|
|
|
|
// Firefox144 sets the JA3/4 fingerprint to mimic Firefox version 144.
|
|
func (j *JA) Firefox144() *Builder { return j.SetHelloSpec(firefox.HelloFirefox_144) }
|
|
|
|
// FirefoxPrivate144 sets the JA3/4 fingerprint to mimic Firefox private version 144.
|
|
func (j *JA) FirefoxPrivate144() *Builder { return j.SetHelloSpec(firefox.HelloFirefoxPrivate_144) }
|
|
|
|
// Tor sets the JA3/4 fingerprint to mimic Tor Browser version 14.5.6.
|
|
func (j *JA) Tor() *Builder { return j.SetHelloSpec(firefox.Tor) }
|
|
|
|
// TorPrivate sets the JA3/4 fingerprint to mimic Tor Browser private version 14.5.6.
|
|
func (j *JA) TorPrivate() *Builder { return j.SetHelloSpec(firefox.TorPrivate) }
|
|
|
|
// IOS sets the JA3/4 fingerprint to mimic the latest iOS Safari browser (auto-detection).
|
|
func (j *JA) IOS() *Builder { return j.SetHelloID(utls.HelloIOS_Auto) }
|
|
|
|
// IOS11 sets the JA3/4 fingerprint to mimic iOS 11.1 Safari.
|
|
func (j *JA) IOS11() *Builder { return j.SetHelloID(utls.HelloIOS_11_1) }
|
|
|
|
// IOS12 sets the JA3/4 fingerprint to mimic iOS 12.1 Safari.
|
|
func (j *JA) IOS12() *Builder { return j.SetHelloID(utls.HelloIOS_12_1) }
|
|
|
|
// IOS13 sets the JA3/4 fingerprint to mimic iOS 13 Safari.
|
|
func (j *JA) IOS13() *Builder { return j.SetHelloID(utls.HelloIOS_13) }
|
|
|
|
// IOS14 sets the JA3/4 fingerprint to mimic iOS 14 Safari.
|
|
func (j *JA) IOS14() *Builder { return j.SetHelloID(utls.HelloIOS_14) }
|
|
|
|
// Randomized sets a completely randomized JA3/4 fingerprint.
|
|
func (j *JA) Randomized() *Builder { return j.SetHelloID(utls.HelloRandomized) }
|
|
|
|
// RandomizedALPN sets a randomized JA3/4 fingerprint with ALPN (Application-Layer Protocol Negotiation).
|
|
func (j *JA) RandomizedALPN() *Builder { return j.SetHelloID(utls.HelloRandomizedALPN) }
|
|
|
|
// RandomizedNoALPN sets a randomized JA3/4 fingerprint without ALPN.
|
|
func (j *JA) RandomizedNoALPN() *Builder { return j.SetHelloID(utls.HelloRandomizedNoALPN) }
|
|
|
|
// Safari sets the JA3/4 fingerprint to mimic the latest Safari browser (auto-detection).
|
|
func (j *JA) Safari() *Builder { return j.SetHelloID(utls.HelloSafari_Auto) }
|