add TLS fingerprint (ja3) spoofer request modifier
This commit is contained in:
@@ -9,7 +9,10 @@ import (
|
||||
func MasqueradeAsGoogleBot() proxychain.RequestModification {
|
||||
const botUA string = "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Googlebot/2.1; http://www.google.com/bot.html) Chrome/79.0.3945.120 Safari/537.36"
|
||||
const botIP string = "66.249.78.8" // TODO: create a random ip pool from https://developers.google.com/static/search/apis/ipranges/googlebot.json
|
||||
return masqueradeAsTrustedBot(botUA, botIP)
|
||||
// https://github.com/trisulnsm/trisul-scripts/blob/master/lua/frontend_scripts/reassembly/ja3/prints/ja3fingerprint.json
|
||||
const ja3 string = "769,49195-49199-49200-49161-49171-49162-49172-156-157-47-10-53-51-57,65281-0-23-35-13-13172-11-10,29-23-24,0"
|
||||
|
||||
return masqueradeAsTrustedBot(botUA, botIP, ja3)
|
||||
}
|
||||
|
||||
// MasqueradeAsBingBot modifies user agent and x-forwarded for
|
||||
@@ -17,7 +20,7 @@ func MasqueradeAsGoogleBot() proxychain.RequestModification {
|
||||
func MasqueradeAsBingBot() proxychain.RequestModification {
|
||||
const botUA string = "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) Chrome/79.0.3945.120 Safari/537.36"
|
||||
const botIP string = "13.66.144.9" // https://www.bing.com/toolbox/bingbot.json
|
||||
return masqueradeAsTrustedBot(botUA, botIP)
|
||||
return masqueradeAsTrustedBot(botUA, botIP, "")
|
||||
}
|
||||
|
||||
// MasqueradeAsWaybackMachineBot modifies user agent and x-forwarded for
|
||||
@@ -25,7 +28,7 @@ func MasqueradeAsBingBot() proxychain.RequestModification {
|
||||
func MasqueradeAsWaybackMachineBot() proxychain.RequestModification {
|
||||
const botUA string = "Mozilla/5.0 (compatible; archive.org_bot +http://www.archive.org/details/archive.org_bot)"
|
||||
const botIP string = "207.241.235.164"
|
||||
return masqueradeAsTrustedBot(botUA, botIP)
|
||||
return masqueradeAsTrustedBot(botUA, botIP, "")
|
||||
}
|
||||
|
||||
// MasqueradeAsFacebookBot modifies user agent and x-forwarded for
|
||||
@@ -34,7 +37,8 @@ func MasqueradeAsFacebookBot() proxychain.RequestModification {
|
||||
const botUA string = "facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)"
|
||||
// 31.13.97.0/24, 31.13.99.0/24, 31.13.100.0/24, 66.220.144.0/20, 69.63.189.0/24, 69.63.190.0/24, 69.171.224.0/20, 69.171.240.0/21, 69.171.248.0/24, 173.252.73.0/24, 173.252.74.0/24, 173.252.77.0/24, 173.252.100.0/22, 173.252.104.0/21, 173.252.112.0/24, 2a03:2880:10::/48, 2a03:2880:10ff::/48, 2a03:2880:11::/48, 2a03:2880:11ff::/48, 2a03:2880:20::/48, 2a03:2880:20ff::/48, 2a03:2880:21ff::/48, 2a03:2880:30ff::/48, 2a03:2880:31ff::/48, 2a03:2880:1010::/48, 2a03:2880:1020::/48, 2a03:2880:2020::/48, 2a03:2880:2050::/48, 2a03:2880:2040::/48, 2a03:2880:2110::/48, 2a03:2880:2130::/48, 2a03:2880:3010::/48, 2a03:2880:3020::/48
|
||||
const botIP string = "31.13.99.8"
|
||||
return masqueradeAsTrustedBot(botUA, botIP)
|
||||
const ja3 string = "771,49199-49195-49171-49161-49200-49196-49172-49162-51-57-50-49169-49159-47-53-10-5-4-255,0-11-10-13-13172-16,23-25-28-27-24-26-22-14-13-11-12-9-10,0-1-2"
|
||||
return masqueradeAsTrustedBot(botUA, botIP, ja3)
|
||||
}
|
||||
|
||||
// MasqueradeAsYandexBot modifies user agent and x-forwarded for
|
||||
@@ -43,7 +47,8 @@ func MasqueradeAsYandexBot() proxychain.RequestModification {
|
||||
const botUA string = "Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)"
|
||||
// 100.43.90.0/24, 37.9.115.0/24, 37.140.165.0/24, 77.88.22.0/25, 77.88.29.0/24, 77.88.31.0/24, 77.88.59.0/24, 84.201.146.0/24, 84.201.148.0/24, 84.201.149.0/24, 87.250.243.0/24, 87.250.253.0/24, 93.158.147.0/24, 93.158.148.0/24, 93.158.151.0/24, 93.158.153.0/32, 95.108.128.0/24, 95.108.138.0/24, 95.108.150.0/23, 95.108.158.0/24, 95.108.156.0/24, 95.108.188.128/25, 95.108.234.0/24, 95.108.248.0/24, 100.43.80.0/24, 130.193.62.0/24, 141.8.153.0/24, 178.154.165.0/24, 178.154.166.128/25, 178.154.173.29, 178.154.200.158, 178.154.202.0/24, 178.154.205.0/24, 178.154.239.0/24, 178.154.243.0/24, 37.9.84.253, 199.21.99.99, 178.154.162.29, 178.154.203.251, 178.154.211.250, 178.154.171.0/24, 178.154.200.0/24, 178.154.244.0/24, 178.154.246.0/24, 95.108.181.0/24, 95.108.246.252, 5.45.254.0/24, 5.255.253.0/24, 37.140.141.0/24, 37.140.188.0/24, 100.43.81.0/24, 100.43.85.0/24, 100.43.91.0/24, 199.21.99.0/24, 2a02:6b8:b000::/32, 2a02:6b8:b010::/32, 2a02:6b8:b011::/32, 2a02:6b8:c0e::/32
|
||||
const botIP string = "37.9.115.9"
|
||||
return masqueradeAsTrustedBot(botUA, botIP)
|
||||
const ja3 string = "769,49200-49196-49192-49188-49172-49162-165-163-161-159-107-106-105-104-57-56-55-54-136-135-134-133-49202-49198-49194-49190-49167-49157-157-61-53-132-49199-49195-49191-49187-49171-49161-164-162-160-158-103-64-63-62-51-50-49-48-154-153-152-151-69-68-67-66-49201-49197-49193-49189-49166-49156-156-60-47-150-65-7-49169-49159-49164-49154-5-4-49170-49160-22-19-16-13-49165-49155-10-255,0-11-10-35-13-15,23-25-28-27-24-26-22-14-13-11-12-9-10,0-1-2"
|
||||
return masqueradeAsTrustedBot(botUA, botIP, ja3)
|
||||
}
|
||||
|
||||
// MasqueradeAsBaiduBot modifies user agent and x-forwarded for
|
||||
@@ -52,7 +57,7 @@ func MasqueradeAsBaiduBot() proxychain.RequestModification {
|
||||
const botUA string = "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"
|
||||
// 180.76.15.0/24, 119.63.196.0/24, 115.239.212./24, 119.63.199.0/24, 122.81.208.0/22, 123.125.71.0/24, 180.76.4.0/24, 180.76.5.0/24, 180.76.6.0/24, 185.10.104.0/24, 220.181.108.0/24, 220.181.51.0/24, 111.13.102.0/24, 123.125.67.144/29, 123.125.67.152/31, 61.135.169.0/24, 123.125.68.68/30, 123.125.68.72/29, 123.125.68.80/28, 123.125.68.96/30, 202.46.48.0/20, 220.181.38.0/24, 123.125.68.80/30, 123.125.68.84/31, 123.125.68.0/24
|
||||
const botIP string = "180.76.15.7"
|
||||
return masqueradeAsTrustedBot(botUA, botIP)
|
||||
return masqueradeAsTrustedBot(botUA, botIP, "")
|
||||
}
|
||||
|
||||
// MasqueradeAsDuckDuckBot modifies user agent and x-forwarded for
|
||||
@@ -61,7 +66,7 @@ func MasqueradeAsDuckDuckBot() proxychain.RequestModification {
|
||||
const botUA string = "DuckDuckBot/1.0; (+http://duckduckgo.com/duckduckbot.html)"
|
||||
// 46.51.197.88, 46.51.197.89, 50.18.192.250, 50.18.192.251, 107.21.1.61, 176.34.131.233, 176.34.135.167, 184.72.106.52, 184.72.115.86
|
||||
const botIP string = "46.51.197.88"
|
||||
return masqueradeAsTrustedBot(botUA, botIP)
|
||||
return masqueradeAsTrustedBot(botUA, botIP, "")
|
||||
}
|
||||
|
||||
// MasqueradeAsYahooBot modifies user agent and x-forwarded for
|
||||
@@ -69,11 +74,12 @@ func MasqueradeAsDuckDuckBot() proxychain.RequestModification {
|
||||
func MasqueradeAsYahooBot() proxychain.RequestModification {
|
||||
const botUA string = "Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)"
|
||||
// 5.255.250.0/24, 37.9.87.0/24, 67.195.37.0/24, 67.195.50.0/24, 67.195.110.0/24, 67.195.111.0/24, 67.195.112.0/23, 67.195.114.0/24, 67.195.115.0/24, 68.180.224.0/21, 72.30.132.0/24, 72.30.142.0/24, 72.30.161.0/24, 72.30.196.0/24, 72.30.198.0/24, 74.6.254.0/24, 74.6.8.0/24, 74.6.13.0/24, 74.6.17.0/24, 74.6.18.0/24, 74.6.22.0/24, 74.6.27.0/24, 74.6.168.0/24, 77.88.5.0/24, 77.88.47.0/24, 93.158.161.0/24, 98.137.72.0/24, 98.137.206.0/24, 98.137.207.0/24, 98.139.168.0/24, 114.111.95.0/24, 124.83.159.0/24, 124.83.179.0/24, 124.83.223.0/24, 141.8.144.0/24, 183.79.63.0/24, 183.79.92.0/24, 203.216.255.0/24, 211.14.11.0/24
|
||||
const ja3 = "769,49200-49196-49192-49188-49172-49162-163-159-107-106-57-56-136-135-49202-49198-49194-49190-49167-49157-157-61-53-132-49199-49195-49191-49187-49171-49161-162-158-103-64-51-50-49170-49160-154-153-69-68-22-19-49201-49197-49193-49189-49166-49156-49165-49155-156-60-47-150-65-10-7-49169-49159-49164-49154-5-4-255,0-11-10-13-15,25-24-23,0-1-2"
|
||||
const botIP string = "37.9.87.5"
|
||||
return masqueradeAsTrustedBot(botUA, botIP)
|
||||
return masqueradeAsTrustedBot(botUA, botIP, ja3)
|
||||
}
|
||||
|
||||
func masqueradeAsTrustedBot(botUA string, botIP string) proxychain.RequestModification {
|
||||
func masqueradeAsTrustedBot(botUA string, botIP string, ja3 string) proxychain.RequestModification {
|
||||
return func(chain *proxychain.ProxyChain) error {
|
||||
chain.AddOnceRequestModifications(
|
||||
SpoofUserAgent(botUA),
|
||||
@@ -81,6 +87,14 @@ func masqueradeAsTrustedBot(botUA string, botIP string) proxychain.RequestModifi
|
||||
DeleteRequestHeader("referrer"),
|
||||
DeleteRequestHeader("origin"),
|
||||
)
|
||||
|
||||
if ja3 != "" {
|
||||
chain.AddOnceRequestModifications(
|
||||
//SpoofJA3fingerprint(ja3, botUA),
|
||||
SpoofJA3fingerprint(ja3, ""),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package requestmodifers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
//"net/http"
|
||||
http "github.com/Danny-Dasilva/fhttp"
|
||||
|
||||
"ladder/proxychain"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
//"net/http"
|
||||
http "github.com/Danny-Dasilva/fhttp"
|
||||
"time"
|
||||
|
||||
"ladder/proxychain"
|
||||
|
||||
451
proxychain/requestmodifers/spoof_ja3_fingerprint.go
Normal file
451
proxychain/requestmodifers/spoof_ja3_fingerprint.go
Normal file
@@ -0,0 +1,451 @@
|
||||
package requestmodifers
|
||||
|
||||
import (
|
||||
"ladder/proxychain"
|
||||
|
||||
"strconv"
|
||||
|
||||
"crypto/sha256"
|
||||
|
||||
// "crypto/tls"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
http "github.com/Danny-Dasilva/fhttp"
|
||||
"github.com/Danny-Dasilva/fhttp/http2"
|
||||
utls "github.com/Danny-Dasilva/utls"
|
||||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
// SpoofJA3fingerprint modifies the TLS client and user agent to spoof a particular JA3 fingerprint
|
||||
// Some anti-bot WAFs such as cloudflare can fingerprint the fields of the TLS hello packet, and the order in which they appear
|
||||
// https://web.archive.org/web/20231126224326/https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967/
|
||||
// https://web.archive.org/web/20231119065253/https://developers.cloudflare.com/bots/concepts/ja3-fingerprint/
|
||||
func SpoofJA3fingerprint(ja3 string, userAgent string) proxychain.RequestModification {
|
||||
//fmt.Println(ja3)
|
||||
return func(chain *proxychain.ProxyChain) error {
|
||||
spoofConfig := browser{
|
||||
UserAgent: userAgent,
|
||||
JA3: ja3,
|
||||
InsecureSkipVerify: false,
|
||||
}
|
||||
|
||||
// deep copy existing client while modifying http transport
|
||||
ja3SpoofClient := &http.Client{
|
||||
//Transport: newJA3SpooferTransport(spoofConfig, proxy),
|
||||
Transport: newJA3SpooferTransport(spoofConfig),
|
||||
Timeout: chain.Client.Timeout,
|
||||
CheckRedirect: chain.Client.CheckRedirect,
|
||||
}
|
||||
|
||||
chain.SetOnceHTTPClient(ja3SpoofClient)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SpoofJA3fingerprintWithProxy modifies the TLS client and user agent to spoof a particular JA3 fingerprint and use a proxy.ContextDialer from the "golang.org/x/net/proxy"
|
||||
// Some anti-bot WAFs such as cloudflare can fingerprint the fields of the TLS hello packet, and the order in which they appear
|
||||
// https://web.archive.org/web/20231126224326/https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967/
|
||||
// https://web.archive.org/web/20231119065253/https://developers.cloudflare.com/bots/concepts/ja3-fingerprint/
|
||||
func SpoofJA3fingerprintWithProxy(ja3 string, userAgent string, proxy proxy.ContextDialer) proxychain.RequestModification {
|
||||
return func(chain *proxychain.ProxyChain) error {
|
||||
spoofConfig := browser{
|
||||
UserAgent: userAgent,
|
||||
JA3: ja3,
|
||||
InsecureSkipVerify: false,
|
||||
}
|
||||
|
||||
// deep copy existing client while modifying http transport
|
||||
ja3SpoofClient := &http.Client{
|
||||
//Transport: newJA3SpooferTransport(spoofConfig, proxy),
|
||||
Transport: newJA3SpooferTransport(spoofConfig, proxy),
|
||||
Timeout: chain.Client.Timeout,
|
||||
CheckRedirect: chain.Client.CheckRedirect,
|
||||
}
|
||||
|
||||
chain.SetOnceHTTPClient(ja3SpoofClient)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================================== //
|
||||
// The following code is adapated from https://github.com/Danny-Dasilva/CycleTLS with GPL3 license
|
||||
// ============================================================================================== //
|
||||
var errProtocolNegotiated = errors.New("protocol negotiated")
|
||||
|
||||
type roundTripper struct {
|
||||
sync.Mutex
|
||||
// fix typing
|
||||
JA3 string
|
||||
UserAgent string
|
||||
|
||||
InsecureSkipVerify bool
|
||||
cachedConnections map[string]net.Conn
|
||||
cachedTransports map[string]http.RoundTripper
|
||||
|
||||
dialer proxy.ContextDialer
|
||||
}
|
||||
|
||||
func (rt *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req.Header.Set("User-Agent", rt.UserAgent)
|
||||
addr := rt.getDialTLSAddr(req)
|
||||
if _, ok := rt.cachedTransports[addr]; !ok {
|
||||
if err := rt.getTransport(req, addr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return rt.cachedTransports[addr].RoundTrip(req)
|
||||
}
|
||||
|
||||
func (rt *roundTripper) getTransport(req *http.Request, addr string) error {
|
||||
switch strings.ToLower(req.URL.Scheme) {
|
||||
case "http":
|
||||
rt.cachedTransports[addr] = &http.Transport{DialContext: rt.dialer.DialContext, DisableKeepAlives: true}
|
||||
return nil
|
||||
case "https":
|
||||
default:
|
||||
return fmt.Errorf("invalid URL scheme: [%v]", req.URL.Scheme)
|
||||
}
|
||||
|
||||
_, err := rt.dialTLS(req.Context(), "tcp", addr)
|
||||
switch err {
|
||||
case errProtocolNegotiated:
|
||||
case nil:
|
||||
// Should never happen.
|
||||
panic("dialTLS returned no error when determining cachedTransports")
|
||||
default:
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rt *roundTripper) dialTLS(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
rt.Lock()
|
||||
defer rt.Unlock()
|
||||
|
||||
// If we have the connection from when we determined the HTTPS
|
||||
// cachedTransports to use, return that.
|
||||
if conn := rt.cachedConnections[addr]; conn != nil {
|
||||
return conn, nil
|
||||
}
|
||||
rawConn, err := rt.dialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var host string
|
||||
if host, _, err = net.SplitHostPort(addr); err != nil {
|
||||
host = addr
|
||||
}
|
||||
//////////////////
|
||||
|
||||
spec, err := StringToSpec(rt.JA3, rt.UserAgent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn := utls.UClient(rawConn, &utls.Config{ServerName: host, InsecureSkipVerify: rt.InsecureSkipVerify}, // MinVersion: tls.VersionTLS10,
|
||||
// MaxVersion: tls.VersionTLS13,
|
||||
|
||||
utls.HelloCustom)
|
||||
|
||||
if err := conn.ApplyPreset(spec); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = conn.Handshake(); err != nil {
|
||||
_ = conn.Close()
|
||||
|
||||
if err.Error() == "tls: CurvePreferences includes unsupported curve" {
|
||||
//fix this
|
||||
return nil, fmt.Errorf("conn.Handshake() error for tls 1.3 (please retry request): %+v", err)
|
||||
}
|
||||
return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
|
||||
}
|
||||
|
||||
//////////
|
||||
if rt.cachedTransports[addr] != nil {
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// No http.Transport constructed yet, create one based on the results
|
||||
// of ALPN.
|
||||
switch conn.ConnectionState().NegotiatedProtocol {
|
||||
case http2.NextProtoTLS:
|
||||
parsedUserAgent := parseUserAgent(rt.UserAgent).UserAgent
|
||||
t2 := http2.Transport{DialTLS: rt.dialTLSHTTP2,
|
||||
PushHandler: &http2.DefaultPushHandler{},
|
||||
Navigator: parsedUserAgent,
|
||||
}
|
||||
rt.cachedTransports[addr] = &t2
|
||||
default:
|
||||
// Assume the remote peer is speaking HTTP 1.x + TLS.
|
||||
rt.cachedTransports[addr] = &http.Transport{DialTLSContext: rt.dialTLS}
|
||||
|
||||
}
|
||||
|
||||
// Stash the connection just established for use servicing the
|
||||
// actual request (should be near-immediate).
|
||||
rt.cachedConnections[addr] = conn
|
||||
|
||||
return nil, errProtocolNegotiated
|
||||
}
|
||||
|
||||
func (rt *roundTripper) dialTLSHTTP2(network, addr string, _ *utls.Config) (net.Conn, error) {
|
||||
return rt.dialTLS(context.Background(), network, addr)
|
||||
}
|
||||
|
||||
func (rt *roundTripper) getDialTLSAddr(req *http.Request) string {
|
||||
host, port, err := net.SplitHostPort(req.URL.Host)
|
||||
if err == nil {
|
||||
return net.JoinHostPort(host, port)
|
||||
}
|
||||
return net.JoinHostPort(req.URL.Host, "443") // we can assume port is 443 at this point
|
||||
}
|
||||
|
||||
func (rt *roundTripper) CloseIdleConnections() {
|
||||
for addr, conn := range rt.cachedConnections {
|
||||
_ = conn.Close()
|
||||
delete(rt.cachedConnections, addr)
|
||||
}
|
||||
}
|
||||
|
||||
func newJA3SpooferTransport(browser browser, dialer ...proxy.ContextDialer) http.RoundTripper {
|
||||
if dialer != nil && len(dialer) > 0 {
|
||||
return &roundTripper{
|
||||
dialer: dialer[0],
|
||||
JA3: browser.JA3,
|
||||
UserAgent: browser.UserAgent,
|
||||
cachedTransports: make(map[string]http.RoundTripper),
|
||||
cachedConnections: make(map[string]net.Conn),
|
||||
InsecureSkipVerify: browser.InsecureSkipVerify,
|
||||
}
|
||||
}
|
||||
|
||||
return &roundTripper{
|
||||
dialer: proxy.Direct,
|
||||
|
||||
JA3: browser.JA3,
|
||||
UserAgent: browser.UserAgent,
|
||||
cachedTransports: make(map[string]http.RoundTripper),
|
||||
cachedConnections: make(map[string]net.Conn),
|
||||
InsecureSkipVerify: browser.InsecureSkipVerify,
|
||||
}
|
||||
}
|
||||
|
||||
type browser struct {
|
||||
// Return a greeting that embeds the name in a message.
|
||||
JA3 string
|
||||
UserAgent string
|
||||
InsecureSkipVerify bool
|
||||
}
|
||||
|
||||
// adapted from
|
||||
// https://github.com/Danny-Dasilva/CycleTLS/blob/963daacd92414dc84a0ec6930dd252381cb1247a/cycletls/utils.go#L27
|
||||
const (
|
||||
chrome = "chrome" //chrome User agent enum
|
||||
firefox = "firefox" //firefox User agent enum
|
||||
)
|
||||
|
||||
type UserAgent struct {
|
||||
UserAgent string
|
||||
HeaderOrder []string
|
||||
}
|
||||
|
||||
// ParseUserAgent returns the pseudo header order and user agent string for chrome/firefox
|
||||
func parseUserAgent(userAgent string) UserAgent {
|
||||
switch {
|
||||
case strings.Contains(strings.ToLower(userAgent), "chrome"):
|
||||
return UserAgent{chrome, []string{":method", ":authority", ":scheme", ":path"}}
|
||||
case strings.Contains(strings.ToLower(userAgent), "firefox"):
|
||||
return UserAgent{firefox, []string{":method", ":path", ":authority", ":scheme"}}
|
||||
default:
|
||||
return UserAgent{chrome, []string{":method", ":authority", ":scheme", ":path"}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ==============
|
||||
|
||||
// StringToSpec creates a ClientHelloSpec based on a JA3 string
|
||||
func StringToSpec(ja3 string, userAgent string) (*utls.ClientHelloSpec, error) {
|
||||
parsedUserAgent := parseUserAgent(userAgent).UserAgent
|
||||
extMap := genMap()
|
||||
tokens := strings.Split(ja3, ",")
|
||||
|
||||
version := tokens[0]
|
||||
ciphers := strings.Split(tokens[1], "-")
|
||||
extensions := strings.Split(tokens[2], "-")
|
||||
curves := strings.Split(tokens[3], "-")
|
||||
if len(curves) == 1 && curves[0] == "" {
|
||||
curves = []string{}
|
||||
}
|
||||
pointFormats := strings.Split(tokens[4], "-")
|
||||
if len(pointFormats) == 1 && pointFormats[0] == "" {
|
||||
pointFormats = []string{}
|
||||
}
|
||||
// parse curves
|
||||
var targetCurves []utls.CurveID
|
||||
targetCurves = append(targetCurves, utls.CurveID(utls.GREASE_PLACEHOLDER)) //append grease for Chrome browsers
|
||||
for _, c := range curves {
|
||||
cid, err := strconv.ParseUint(c, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
targetCurves = append(targetCurves, utls.CurveID(cid))
|
||||
// if cid != uint64(utls.CurveP521) {
|
||||
// CurveP521 sometimes causes handshake errors
|
||||
// }
|
||||
}
|
||||
extMap["10"] = &utls.SupportedCurvesExtension{Curves: targetCurves}
|
||||
|
||||
// parse point formats
|
||||
var targetPointFormats []byte
|
||||
for _, p := range pointFormats {
|
||||
pid, err := strconv.ParseUint(p, 10, 8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
targetPointFormats = append(targetPointFormats, byte(pid))
|
||||
}
|
||||
extMap["11"] = &utls.SupportedPointsExtension{SupportedPoints: targetPointFormats}
|
||||
|
||||
// set extension 43
|
||||
vid64, err := strconv.ParseUint(version, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vid := uint16(vid64)
|
||||
// extMap["43"] = &utls.SupportedVersionsExtension{
|
||||
// Versions: []uint16{
|
||||
// utls.VersionTLS12,
|
||||
// },
|
||||
// }
|
||||
|
||||
// build extenions list
|
||||
var exts []utls.TLSExtension
|
||||
//Optionally Add Chrome Grease Extension
|
||||
if parsedUserAgent == chrome {
|
||||
exts = append(exts, &utls.UtlsGREASEExtension{})
|
||||
}
|
||||
for _, e := range extensions {
|
||||
te, ok := extMap[e]
|
||||
if !ok {
|
||||
return nil, errors.New("string2spec extension error")
|
||||
}
|
||||
// //Optionally add Chrome Grease Extension
|
||||
if e == "21" && parsedUserAgent == chrome {
|
||||
exts = append(exts, &utls.UtlsGREASEExtension{})
|
||||
}
|
||||
exts = append(exts, te)
|
||||
}
|
||||
//Add this back in if user agent is chrome and no padding extension is given
|
||||
// if parsedUserAgent == chrome {
|
||||
// exts = append(exts, &utls.UtlsGREASEExtension{})
|
||||
// exts = append(exts, &utls.UtlsPaddingExtension{GetPaddingLen: utls.BoringPaddingStyle})
|
||||
// }
|
||||
// build SSLVersion
|
||||
// vid64, err := strconv.ParseUint(version, 10, 16)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// build CipherSuites
|
||||
var suites []uint16
|
||||
//Optionally Add Chrome Grease Extension
|
||||
if parsedUserAgent == chrome {
|
||||
suites = append(suites, utls.GREASE_PLACEHOLDER)
|
||||
}
|
||||
for _, c := range ciphers {
|
||||
cid, err := strconv.ParseUint(c, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
suites = append(suites, uint16(cid))
|
||||
}
|
||||
_ = vid
|
||||
return &utls.ClientHelloSpec{
|
||||
// TLSVersMin: vid,
|
||||
// TLSVersMax: vid,
|
||||
CipherSuites: suites,
|
||||
CompressionMethods: []byte{0},
|
||||
Extensions: exts,
|
||||
GetSessionID: sha256.Sum256,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func genMap() (extMap map[string]utls.TLSExtension) {
|
||||
extMap = map[string]utls.TLSExtension{
|
||||
"0": &utls.SNIExtension{},
|
||||
"5": &utls.StatusRequestExtension{},
|
||||
// These are applied later
|
||||
// "10": &tls.SupportedCurvesExtension{...}
|
||||
// "11": &tls.SupportedPointsExtension{...}
|
||||
"13": &utls.SignatureAlgorithmsExtension{
|
||||
SupportedSignatureAlgorithms: []utls.SignatureScheme{
|
||||
utls.ECDSAWithP256AndSHA256,
|
||||
utls.ECDSAWithP384AndSHA384,
|
||||
utls.ECDSAWithP521AndSHA512,
|
||||
utls.PSSWithSHA256,
|
||||
utls.PSSWithSHA384,
|
||||
utls.PSSWithSHA512,
|
||||
utls.PKCS1WithSHA256,
|
||||
utls.PKCS1WithSHA384,
|
||||
utls.PKCS1WithSHA512,
|
||||
utls.ECDSAWithSHA1,
|
||||
utls.PKCS1WithSHA1,
|
||||
},
|
||||
},
|
||||
"16": &utls.ALPNExtension{
|
||||
AlpnProtocols: []string{"h2", "http/1.1"},
|
||||
},
|
||||
"17": &utls.GenericExtension{Id: 17}, // status_request_v2
|
||||
"18": &utls.SCTExtension{},
|
||||
"21": &utls.UtlsPaddingExtension{GetPaddingLen: utls.BoringPaddingStyle},
|
||||
"22": &utls.GenericExtension{Id: 22}, // encrypt_then_mac
|
||||
"23": &utls.UtlsExtendedMasterSecretExtension{},
|
||||
"27": &utls.CompressCertificateExtension{
|
||||
Algorithms: []utls.CertCompressionAlgo{utls.CertCompressionBrotli},
|
||||
},
|
||||
"28": &utls.FakeRecordSizeLimitExtension{}, //Limit: 0x4001
|
||||
"35": &utls.SessionTicketExtension{},
|
||||
"34": &utls.GenericExtension{Id: 34},
|
||||
"41": &utls.GenericExtension{Id: 41}, //FIXME pre_shared_key
|
||||
"43": &utls.SupportedVersionsExtension{Versions: []uint16{
|
||||
utls.GREASE_PLACEHOLDER,
|
||||
utls.VersionTLS13,
|
||||
utls.VersionTLS12,
|
||||
utls.VersionTLS11,
|
||||
utls.VersionTLS10}},
|
||||
"44": &utls.CookieExtension{},
|
||||
"45": &utls.PSKKeyExchangeModesExtension{Modes: []uint8{
|
||||
utls.PskModeDHE,
|
||||
}},
|
||||
"49": &utls.GenericExtension{Id: 49}, // post_handshake_auth
|
||||
"50": &utls.GenericExtension{Id: 50}, // signature_algorithms_cert
|
||||
"51": &utls.KeyShareExtension{KeyShares: []utls.KeyShare{
|
||||
{Group: utls.CurveID(utls.GREASE_PLACEHOLDER), Data: []byte{0}},
|
||||
{Group: utls.X25519},
|
||||
|
||||
// {Group: utls.CurveP384}, known bug missing correct extensions for handshake
|
||||
}},
|
||||
"30032": &utls.GenericExtension{Id: 0x7550, Data: []byte{0}}, //FIXME
|
||||
"13172": &utls.NPNExtension{},
|
||||
"17513": &utls.ApplicationSettingsExtension{
|
||||
SupportedALPNList: []string{
|
||||
"h2",
|
||||
},
|
||||
},
|
||||
"65281": &utls.RenegotiationInfoExtension{
|
||||
Renegotiation: utls.RenegotiateOnceAsClient,
|
||||
},
|
||||
"65037": &utls.GenericExtension{Id: 65037},
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user