├── .gitignore ├── LICENSE ├── README.md ├── api.go ├── api_test.go ├── crypt.go ├── crypt_test.go ├── fingerprint.go ├── fingerprint_test.go ├── go.mod ├── go.sum ├── murmur.go ├── murmur_test.go ├── util.go └── util_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 BigFan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # funcaptcha 2 | 3 | This is a port of project [funcaptcha](https://github.com/noahcoolboy/funcaptcha) to golang. -------------------------------------------------------------------------------- /api.go: -------------------------------------------------------------------------------- 1 | package funcaptcha 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "math/rand" 10 | "net/url" 11 | "strconv" 12 | "strings" 13 | "time" 14 | 15 | http "github.com/bogdanfinn/fhttp" 16 | tls_client "github.com/bogdanfinn/tls-client" 17 | ) 18 | 19 | type GetTokenOptions struct { 20 | PKey string `json:"pkey"` 21 | SURL string `json:"surl,omitempty"` 22 | Data map[string]string `json:"data,omitempty"` 23 | Headers map[string]string `json:"headers,omitempty"` 24 | Site string `json:"site,omitempty"` 25 | Location string `json:"location,omitempty"` 26 | Proxy string `json:"proxy,omitempty"` 27 | } 28 | 29 | type GetTokenResult struct { 30 | ChallengeURL string `json:"challenge_url"` 31 | ChallengeURLCDN string `json:"challenge_url_cdn"` 32 | ChallengeURLCDNSRI string `json:"challenge_url_cdn_sri"` 33 | DisableDefaultStyling bool `json:"disable_default_styling"` 34 | IFrameHeight int `json:"iframe_height"` 35 | IFrameWidth int `json:"iframe_width"` 36 | KBio bool `json:"kbio"` 37 | MBio bool `json:"mbio"` 38 | NoScript string `json:"noscript"` 39 | TBio bool `json:"tbio"` 40 | Token string `json:"token"` 41 | } 42 | 43 | var ( 44 | jar = tls_client.NewCookieJar() 45 | options = []tls_client.HttpClientOption{ 46 | tls_client.WithTimeoutSeconds(360), 47 | tls_client.WithClientProfile(tls_client.Safari_IOS_16_0), 48 | tls_client.WithNotFollowRedirects(), 49 | tls_client.WithCookieJar(jar), // create cookieJar instance and pass it as argument 50 | } 51 | client, _ = tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...) 52 | ) 53 | 54 | func GetToken(options *GetTokenOptions) (GetTokenResult, error) { 55 | if options.SURL == "" { 56 | options.SURL = "https://client-api.arkoselabs.com" 57 | } 58 | if options.Headers == nil { 59 | options.Headers = make(map[string]string) 60 | } 61 | if _, ok := options.Headers["User-Agent"]; !ok { 62 | options.Headers["User-Agent"] = DEFAULT_USER_AGENT 63 | } 64 | 65 | options.Headers["Accept-Language"] = "en-US,en;q=0.9" 66 | options.Headers["Sec-Fetch-Site"] = "same-origin" 67 | options.Headers["Accept"] = "*/*" 68 | options.Headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8" 69 | options.Headers["sec-fetch-mode"] = "cors" 70 | 71 | if options.Site != "" { 72 | options.Headers["Origin"] = options.SURL 73 | options.Headers["Referer"] = fmt.Sprintf("%s/v2/%s/1.4.3/enforcement.%s.html", options.SURL, options.PKey, Random()) 74 | } 75 | 76 | ua := options.Headers["User-Agent"] 77 | formData := url.Values{ 78 | "bda": {GetBda(ua, options.Headers["Referer"], options.Location)}, 79 | "public_key": {options.PKey}, 80 | "site": {options.Site}, 81 | "userbrowser": {ua}, 82 | "rnd": {strconv.FormatFloat(rand.Float64(), 'f', -1, 64)}, 83 | } 84 | 85 | if options.Site == "" { 86 | formData.Del("site") 87 | } 88 | 89 | for key, value := range options.Data { 90 | formData.Add("data["+key+"]", value) 91 | } 92 | 93 | form := strings.ReplaceAll(formData.Encode(), "+", "%20") 94 | form = strings.ReplaceAll(form, "%28", "(") 95 | form = strings.ReplaceAll(form, "%29", ")") 96 | req, err := http.NewRequest("POST", options.SURL+"/fc/gt2/public_key/"+options.PKey, bytes.NewBufferString(form)) 97 | if err != nil { 98 | return GetTokenResult{}, err 99 | } 100 | 101 | for key, value := range options.Headers { 102 | req.Header.Set(key, value) 103 | } 104 | 105 | resp, err := client.Do(req) 106 | if err != nil { 107 | return GetTokenResult{}, err 108 | } 109 | defer resp.Body.Close() 110 | 111 | body, err := io.ReadAll(resp.Body) 112 | if err != nil { 113 | return GetTokenResult{}, err 114 | } 115 | 116 | var result GetTokenResult 117 | err = json.Unmarshal(body, &result) 118 | if err != nil { 119 | return GetTokenResult{}, err 120 | } 121 | 122 | return result, nil 123 | } 124 | 125 | type OpenAiRequest struct { 126 | Request *http.Request 127 | Client *tls_client.HttpClient 128 | } 129 | 130 | func (r *OpenAiRequest) GetToken() (string, error) { 131 | resp, err := (*r.Client).Do(r.Request) 132 | if err != nil { 133 | return "", err 134 | } 135 | defer resp.Body.Close() 136 | 137 | if resp.StatusCode != 200 { 138 | return "", fmt.Errorf("status code: %d", resp.StatusCode) 139 | } 140 | 141 | body, err := io.ReadAll(resp.Body) 142 | if err != nil { 143 | return "", err 144 | } 145 | 146 | var result GetTokenResult 147 | err = json.Unmarshal(body, &result) 148 | if err != nil { 149 | return "", err 150 | } 151 | 152 | return result.Token, nil 153 | } 154 | 155 | func NewOpenAiRequestV1() (*OpenAiRequest, error) { 156 | surl := "https://tcr9i.chat.openai.com" 157 | pkey := "35536E1E-65B4-4D96-9D97-6ADB7EFF8147" 158 | 159 | formData := url.Values{ 160 | "bda": {GetBda(DEFAULT_USER_AGENT, 161 | fmt.Sprintf("%s/v2/%s/1.5.2/enforcement.%s.html", 162 | surl, pkey, Random()), "")}, 163 | "public_key": {"35536E1E-65B4-4D96-9D97-6ADB7EFF8147"}, 164 | "site": {"https://chat.openai.com"}, 165 | "userbrowser": {DEFAULT_USER_AGENT}, 166 | "capi_version": {"1.5.2"}, 167 | "capi_mode": {"lightbox"}, 168 | "style_theme": {"default"}, 169 | "rnd": {strconv.FormatFloat(rand.Float64(), 'f', -1, 64)}, 170 | } 171 | 172 | form := strings.ReplaceAll(formData.Encode(), "+", "%20") 173 | form = strings.ReplaceAll(form, "%28", "(") 174 | form = strings.ReplaceAll(form, "%29", ")") 175 | req, err := http.NewRequest("POST", surl+"/fc/gt2/public_key/"+pkey, bytes.NewBufferString(form)) 176 | if err != nil { 177 | return nil, err 178 | } 179 | 180 | req.Header.Set("Origin", surl) 181 | req.Header.Set("Accept-Language", "en-US,en;q=0.9") 182 | req.Header.Set("Sec-Fetch-Site", "same-origin") 183 | req.Header.Set("Accept", "*/*") 184 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") 185 | req.Header.Set("sec-fetch-mode", "cors") 186 | return &OpenAiRequest{ 187 | Request: req, 188 | Client: &client, 189 | }, nil 190 | } 191 | 192 | func GetOpenAITokenV1() (string, error) { 193 | req, err := NewOpenAiRequestV1() 194 | if err != nil { 195 | return "", err 196 | } 197 | return req.GetToken() 198 | } 199 | 200 | func NewOpenAiRequestV2() (*OpenAiRequest, error) { 201 | // generate timestamp in 1687790752 format 202 | timestamp := fmt.Sprintf("%d", time.Now().UnixNano()/1000000000) 203 | bx := fmt.Sprintf(`[{"key":"api_type","value":"js"},{"key":"p","value":1},{"key":"f","value":"9711bd3695defe0844fb8fd8a722f38b"},{"key":"n","value":"%s"},{"key":"wh","value":"80b13fd48b8da8e4157eeb6f9e9fbedb|5ab5738955e0611421b686bc95655ad0"},{"key":"enhanced_fp","value":[{"key":"webgl_extensions","value":null},{"key":"webgl_extensions_hash","value":null},{"key":"webgl_renderer","value":null},{"key":"webgl_vendor","value":null},{"key":"webgl_version","value":null},{"key":"webgl_shading_language_version","value":null},{"key":"webgl_aliased_line_width_range","value":null},{"key":"webgl_aliased_point_size_range","value":null},{"key":"webgl_antialiasing","value":null},{"key":"webgl_bits","value":null},{"key":"webgl_max_params","value":null},{"key":"webgl_max_viewport_dims","value":null},{"key":"webgl_unmasked_vendor","value":null},{"key":"webgl_unmasked_renderer","value":null},{"key":"webgl_vsf_params","value":null},{"key":"webgl_vsi_params","value":null},{"key":"webgl_fsf_params","value":null},{"key":"webgl_fsi_params","value":null},{"key":"webgl_hash_webgl","value":null},{"key":"user_agent_data_brands","value":null},{"key":"user_agent_data_mobile","value":null},{"key":"navigator_connection_downlink","value":null},{"key":"navigator_connection_downlink_max","value":null},{"key":"network_info_rtt","value":null},{"key":"network_info_save_data","value":null},{"key":"network_info_rtt_type","value":null},{"key":"screen_pixel_depth","value":24},{"key":"navigator_device_memory","value":null},{"key":"navigator_languages","value":"en-US,en"},{"key":"window_inner_width","value":0},{"key":"window_inner_height","value":0},{"key":"window_outer_width","value":0},{"key":"window_outer_height","value":0},{"key":"browser_detection_firefox","value":true},{"key":"browser_detection_brave","value":false},{"key":"audio_codecs","value":"{\"ogg\":\"probably\",\"mp3\":\"maybe\",\"wav\":\"probably\",\"m4a\":\"maybe\",\"aac\":\"maybe\"}"},{"key":"video_codecs","value":"{\"ogg\":\"probably\",\"h264\":\"probably\",\"webm\":\"probably\",\"mpeg4v\":\"\",\"mpeg4a\":\"\",\"theora\":\"\"}"},{"key":"media_query_dark_mode","value":false},{"key":"headless_browser_phantom","value":false},{"key":"headless_browser_selenium","value":false},{"key":"headless_browser_nightmare_js","value":false},{"key":"document__referrer","value":""},{"key":"window__ancestor_origins","value":null},{"key":"window__tree_index","value":[1]},{"key":"window__tree_structure","value":"[[],[]]"},{"key":"window__location_href","value":"https://tcr9i.chat.openai.com/v2/1.5.2/enforcement.64b3a4e29686f93d52816249ecbf9857.html#35536E1E-65B4-4D96-9D97-6ADB7EFF8147"},{"key":"client_config__sitedata_location_href","value":"https://chat.openai.com/"},{"key":"client_config__surl","value":"https://tcr9i.chat.openai.com"},{"key":"mobile_sdk__is_sdk"},{"key":"client_config__language","value":null},{"key":"audio_fingerprint","value":"35.73833402246237"}]},{"key":"fe","value":["DNT:1","L:en-US","D:24","PR:1","S:0,0","AS:false","TO:0","SS:true","LS:true","IDB:true","B:false","ODB:false","CPUC:unknown","PK:Linux x86_64","CFP:330110783","FR:false","FOS:false","FB:false","JSF:Arial,Arial Narrow,Bitstream Vera Sans Mono,Bookman Old Style,Century Schoolbook,Courier,Courier New,Helvetica,MS Gothic,MS PGothic,Palatino,Palatino Linotype,Times,Times New Roman","P:Chrome PDF Viewer,Chromium PDF Viewer,Microsoft Edge PDF Viewer,PDF Viewer,WebKit built-in PDF","T:0,false,false","H:2","SWF:false"]},{"key":"ife_hash","value":"2a007a5daef41ee943d5fc73a0a8c312"},{"key":"cs","value":1},{"key":"jsbd","value":"{\"HL\":2,\"NCE\":true,\"DT\":\"\",\"NWD\":\"false\",\"DOTO\":1,\"DMTO\":1}"}]`, 204 | base64.StdEncoding.EncodeToString([]byte(timestamp))) 205 | // var bt = new Date() ['getTime']() / 1000 206 | bt := time.Now().UnixMicro() / 1000000 207 | // bw = Math.round(bt - (bt % 21600) 208 | bw := strconv.FormatInt(bt-(bt%21600), 10) 209 | bv := "Mozilla/5.0 (X11; Linux x86_64; rv:114.0) Gecko/20100101 Firefox/114.0" 210 | bda := Encrypt(bx, bv+bw) 211 | bda = base64.StdEncoding.EncodeToString([]byte(bda)) 212 | form := url.Values{ 213 | "bda": {bda}, 214 | "public_key": {"35536E1E-65B4-4D96-9D97-6ADB7EFF8147"}, 215 | "site": {"https://chat.openai.com"}, 216 | "userbrowser": {bv}, 217 | "capi_version": {"1.5.2"}, 218 | "capi_mode": {"lightbox"}, 219 | "style_theme": {"default"}, 220 | "rnd": {strconv.FormatFloat(rand.Float64(), 'f', -1, 64)}, 221 | } 222 | req, _ := http.NewRequest(http.MethodPost, "https://tcr9i.chat.openai.com/fc/gt2/public_key/35536E1E-65B4-4D96-9D97-6ADB7EFF8147", strings.NewReader(form.Encode())) 223 | req.Header.Set("Host", "tcr9i.chat.openai.com") 224 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; rv:114.0) Gecko/20100101 Firefox/114.0") 225 | req.Header.Set("Accept", "*/*") 226 | req.Header.Set("Accept-Language", "en-US,en;q=0.5") 227 | req.Header.Set("Accept-Encoding", "gzip, deflate, br") 228 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") 229 | req.Header.Set("Origin", "https://tcr9i.chat.openai.com") 230 | req.Header.Set("DNT", "1") 231 | req.Header.Set("Connection", "keep-alive") 232 | req.Header.Set("Referer", "https://tcr9i.chat.openai.com/v2/1.5.2/enforcement.64b3a4e29686f93d52816249ecbf9857.html") 233 | req.Header.Set("Sec-Fetch-Dest", "empty") 234 | req.Header.Set("Sec-Fetch-Mode", "cors") 235 | req.Header.Set("Sec-Fetch-Site", "same-origin") 236 | req.Header.Set("TE", "trailers") 237 | return &OpenAiRequest{ 238 | Request: req, 239 | Client: &client, 240 | }, nil 241 | } 242 | 243 | func GetOpenAITokenV2() (string, error) { 244 | req, err := NewOpenAiRequestV2() 245 | if err != nil { 246 | return "", err 247 | } 248 | return req.GetToken() 249 | } 250 | -------------------------------------------------------------------------------- /api_test.go: -------------------------------------------------------------------------------- 1 | package funcaptcha 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestGetToken(t *testing.T) { 9 | token, _ := GetOpenAITokenV1() 10 | if !strings.Contains(token, "sup=") { 11 | t.Errorf("Token does not contain 'sup='") 12 | } 13 | if !strings.Contains(token, "rid=") { 14 | t.Errorf("Token does not contain 'rid='") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /crypt.go: -------------------------------------------------------------------------------- 1 | package funcaptcha 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/md5" 8 | "crypto/rand" 9 | "encoding/base64" 10 | "encoding/hex" 11 | "encoding/json" 12 | "errors" 13 | "hash" 14 | ) 15 | 16 | type EncryptionData struct { 17 | Ct string `json:"ct"` 18 | Iv string `json:"iv"` 19 | S string `json:"s"` 20 | } 21 | 22 | func Encrypt(data string, key string) string { 23 | encData, _ := AesEncrypt(data, key) 24 | 25 | encDataJson, err := json.Marshal(encData) 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | return string(encDataJson) 31 | } 32 | 33 | func AesDecrypt(cipherText string, password string) (string, error) { 34 | data, err := base64.StdEncoding.DecodeString(cipherText) 35 | if err != nil { 36 | return "", err 37 | } 38 | if string(data[:8]) != "Salted__" { 39 | return "", errors.New("invalid crypto js aes encryption") 40 | } 41 | 42 | salt := data[8:16] 43 | cipherBytes := data[16:] 44 | key, iv, err := DefaultEvpKDF([]byte(password), salt) 45 | if err != nil { 46 | return "", err 47 | } 48 | 49 | block, err := aes.NewCipher(key) 50 | if err != nil { 51 | return "", err 52 | } 53 | 54 | mode := cipher.NewCBCDecrypter(block, iv) 55 | mode.CryptBlocks(cipherBytes, cipherBytes) 56 | 57 | result := PKCS5UnPadding(cipherBytes) 58 | return string(result), nil 59 | } 60 | 61 | func AesEncrypt(content string, password string) (*EncryptionData, error) { 62 | salt := make([]byte, 8) 63 | _, err := rand.Read(salt) 64 | if err != nil { 65 | return nil, err 66 | } 67 | key, iv, err := DefaultEvpKDF([]byte(password), salt) 68 | 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | block, err := aes.NewCipher(key) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | mode := cipher.NewCBCEncrypter(block, iv) 79 | cipherBytes := PKCS5Padding([]byte(content), aes.BlockSize) 80 | mode.CryptBlocks(cipherBytes, cipherBytes) 81 | 82 | //TODO: remove redundant code 83 | md5Hash := md5.New() 84 | salted := "" 85 | var dx []byte 86 | 87 | for i := 0; i < 3; i++ { 88 | md5Hash.Write(dx) 89 | md5Hash.Write([]byte(password)) 90 | md5Hash.Write(salt) 91 | 92 | dx = md5Hash.Sum(nil) 93 | md5Hash.Reset() 94 | 95 | salted += hex.EncodeToString(dx) 96 | } 97 | 98 | cipherText := base64.StdEncoding.EncodeToString(cipherBytes) 99 | encData := &EncryptionData{ 100 | Ct: cipherText, 101 | Iv: salted[64 : 64+32], 102 | S: hex.EncodeToString(salt), 103 | } 104 | return encData, nil 105 | } 106 | 107 | // https://stackoverflow.com/questions/27677236/encryption-in-javascript-and-decryption-with-php/27678978#27678978 108 | // https://github.com/brix/crypto-js/blob/8e6d15bf2e26d6ff0af5277df2604ca12b60a718/src/evpkdf.js#L55 109 | func EvpKDF(password []byte, salt []byte, keySize int, iterations int, hashAlgorithm string) ([]byte, error) { 110 | var block []byte 111 | var hasher hash.Hash 112 | derivedKeyBytes := make([]byte, 0) 113 | switch hashAlgorithm { 114 | case "md5": 115 | hasher = md5.New() 116 | default: 117 | return []byte{}, errors.New("not implement hasher algorithm") 118 | } 119 | for len(derivedKeyBytes) < keySize*4 { 120 | if len(block) > 0 { 121 | hasher.Write(block) 122 | } 123 | hasher.Write(password) 124 | hasher.Write(salt) 125 | block = hasher.Sum([]byte{}) 126 | hasher.Reset() 127 | 128 | for i := 1; i < iterations; i++ { 129 | hasher.Write(block) 130 | block = hasher.Sum([]byte{}) 131 | hasher.Reset() 132 | } 133 | derivedKeyBytes = append(derivedKeyBytes, block...) 134 | } 135 | return derivedKeyBytes[:keySize*4], nil 136 | } 137 | 138 | func DefaultEvpKDF(password []byte, salt []byte) (key []byte, iv []byte, err error) { 139 | // https://github.com/brix/crypto-js/blob/8e6d15bf2e26d6ff0af5277df2604ca12b60a718/src/cipher-core.js#L775 140 | keySize := 256 / 32 141 | ivSize := 128 / 32 142 | derivedKeyBytes, err := EvpKDF(password, salt, keySize+ivSize, 1, "md5") 143 | if err != nil { 144 | return []byte{}, []byte{}, err 145 | } 146 | return derivedKeyBytes[:keySize*4], derivedKeyBytes[keySize*4:], nil 147 | } 148 | 149 | // https://stackoverflow.com/questions/41579325/golang-how-do-i-decrypt-with-des-cbc-and-pkcs7 150 | func PKCS5UnPadding(src []byte) []byte { 151 | length := len(src) 152 | unpadding := int(src[length-1]) 153 | return src[:(length - unpadding)] 154 | } 155 | 156 | func PKCS5Padding(src []byte, blockSize int) []byte { 157 | padding := blockSize - len(src)%blockSize 158 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 159 | return append(src, padtext...) 160 | } 161 | -------------------------------------------------------------------------------- /crypt_test.go: -------------------------------------------------------------------------------- 1 | package funcaptcha 2 | 3 | import "testing" 4 | 5 | func TestEncryptDecrypt1(t *testing.T) { 6 | enc := Encrypt("test-data", "test-key") 7 | println(enc) 8 | } 9 | -------------------------------------------------------------------------------- /fingerprint.go: -------------------------------------------------------------------------------- 1 | package funcaptcha 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "math" 7 | "math/rand" 8 | "reflect" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | var baseFingerprint = map[string]interface{}{ 14 | "DNT": "unknown", 15 | "L": "en-US", 16 | "D": 24, 17 | "PR": 1, 18 | "S": []int{1920, 1200}, 19 | "AS": []int{1920, 1200}, 20 | "TO": 9999, 21 | "SS": true, 22 | "LS": true, 23 | "IDB": true, 24 | "B": false, 25 | "ODB": true, 26 | "CPUC": "unknown", 27 | "PK": "Win32", 28 | "CFP": fmt.Sprintf("canvas winding:yes~canvas fp:data:image/png;base64,%s", base64.StdEncoding.EncodeToString([]byte(strconv.FormatFloat(rand.Float64(), 'f', -1, 64)))), 29 | "FR": false, 30 | "FOS": false, 31 | "FB": false, 32 | "JSF": "", 33 | "P": []string{ 34 | "Chrome PDF Plugin::Portable Document Format::application/x-google-chrome-pdf~pdf", 35 | "Chrome PDF Viewer::::application/pdf~pdf", 36 | "Native Client::::application/x-nacl~,application/x-pnacl~", 37 | }, 38 | "T": []interface{}{0, false, false}, 39 | "H": 24, 40 | "SWF": false, // Flash support 41 | } 42 | 43 | var languages = []string{ 44 | "af", 45 | "af-ZA", 46 | "ar", 47 | "ar-AE", 48 | "ar-BH", 49 | "ar-DZ", 50 | "ar-EG", 51 | "ar-IQ", 52 | "ar-JO", 53 | "ar-KW", 54 | "ar-LB", 55 | "ar-LY", 56 | "ar-MA", 57 | "ar-OM", 58 | "ar-QA", 59 | "ar-SA", 60 | "ar-SY", 61 | "ar-TN", 62 | "ar-YE", 63 | "az", 64 | "az-AZ", 65 | "az-AZ", 66 | "be", 67 | "be-BY", 68 | "bg", 69 | "bg-BG", 70 | "bs-BA", 71 | "ca", 72 | "ca-ES", 73 | "cs", 74 | "cs-CZ", 75 | "cy", 76 | "cy-GB", 77 | "da", 78 | "da-DK", 79 | "de", 80 | "de-AT", 81 | "de-CH", 82 | "de-DE", 83 | "de-LI", 84 | "de-LU", 85 | "dv", 86 | "dv-MV", 87 | "el", 88 | "el-GR", 89 | "en", 90 | "en-AU", 91 | "en-BZ", 92 | "en-CA", 93 | "en-CB", 94 | "en-GB", 95 | "en-IE", 96 | "en-JM", 97 | "en-NZ", 98 | "en-PH", 99 | "en-TT", 100 | "en-US", 101 | "en-ZA", 102 | "en-ZW", 103 | "eo", 104 | "es", 105 | "es-AR", 106 | "es-BO", 107 | "es-CL", 108 | "es-CO", 109 | "es-CR", 110 | "es-DO", 111 | "es-EC", 112 | "es-ES", 113 | "es-ES", 114 | "es-GT", 115 | "es-HN", 116 | "es-MX", 117 | "es-NI", 118 | "es-PA", 119 | "es-PE", 120 | "es-PR", 121 | "es-PY", 122 | "es-SV", 123 | "es-UY", 124 | "es-VE", 125 | "et", 126 | "et-EE", 127 | "eu", 128 | "eu-ES", 129 | "fa", 130 | "fa-IR", 131 | "fi", 132 | "fi-FI", 133 | "fo", 134 | "fo-FO", 135 | "fr", 136 | "fr-BE", 137 | "fr-CA", 138 | "fr-CH", 139 | "fr-FR", 140 | "fr-LU", 141 | "fr-MC", 142 | "gl", 143 | "gl-ES", 144 | "gu", 145 | "gu-IN", 146 | "he", 147 | "he-IL", 148 | "hi", 149 | "hi-IN", 150 | "hr", 151 | "hr-BA", 152 | "hr-HR", 153 | "hu", 154 | "hu-HU", 155 | "hy", 156 | "hy-AM", 157 | "id", 158 | "id-ID", 159 | "is", 160 | "is-IS", 161 | "it", 162 | "it-CH", 163 | "it-IT", 164 | "ja", 165 | "ja-JP", 166 | "ka", 167 | "ka-GE", 168 | "kk", 169 | "kk-KZ", 170 | "kn", 171 | "kn-IN", 172 | "ko", 173 | "ko-KR", 174 | "kok", 175 | "kok-IN", 176 | "ky", 177 | "ky-KG", 178 | "lt", 179 | "lt-LT", 180 | "lv", 181 | "lv-LV", 182 | "mi", 183 | "mi-NZ", 184 | "mk", 185 | "mk-MK", 186 | "mn", 187 | "mn-MN", 188 | "mr", 189 | "mr-IN", 190 | "ms", 191 | "ms-BN", 192 | "ms-MY", 193 | "mt", 194 | "mt-MT", 195 | "nb", 196 | "nb-NO", 197 | "nl", 198 | "nl-BE", 199 | "nl-NL", 200 | "nn-NO", 201 | "ns", 202 | "ns-ZA", 203 | "pa", 204 | "pa-IN", 205 | "pl", 206 | "pl-PL", 207 | "ps", 208 | "ps-AR", 209 | "pt", 210 | "pt-BR", 211 | "pt-PT", 212 | "qu", 213 | "qu-BO", 214 | "qu-EC", 215 | "qu-PE", 216 | "ro", 217 | "ro-RO", 218 | "ru", 219 | "ru-RU", 220 | "sa", 221 | "sa-IN", 222 | "se", 223 | "se-FI", 224 | "se-FI", 225 | "se-FI", 226 | "se-NO", 227 | "se-NO", 228 | "se-NO", 229 | "se-SE", 230 | "se-SE", 231 | "se-SE", 232 | "sk", 233 | "sk-SK", 234 | "sl", 235 | "sl-SI", 236 | "sq", 237 | "sq-AL", 238 | "sr-BA", 239 | "sr-BA", 240 | "sr-SP", 241 | "sr-SP", 242 | "sv", 243 | "sv-FI", 244 | "sv-SE", 245 | "sw", 246 | "sw-KE", 247 | "syr", 248 | "syr-SY", 249 | "ta", 250 | "ta-IN", 251 | "te", 252 | "te-IN", 253 | "th", 254 | "th-TH", 255 | "tl", 256 | "tl-PH", 257 | "tn", 258 | "tn-ZA", 259 | "tr", 260 | "tr-TR", 261 | "tt", 262 | "tt-RU", 263 | "ts", 264 | "uk", 265 | "uk-UA", 266 | "ur", 267 | "ur-PK", 268 | "uz", 269 | "uz-UZ", 270 | "uz-UZ", 271 | "vi", 272 | "vi-VN", 273 | "xh", 274 | "xh-ZA", 275 | "zh", 276 | "zh-CN", 277 | "zh-HK", 278 | "zh-MO", 279 | "zh-SG", 280 | "zh-TW", 281 | "zu", 282 | "zu-ZA", 283 | } 284 | 285 | var screenRes = [][]int{ 286 | {1920, 1080}, 287 | {1920, 1200}, 288 | {2048, 1080}, 289 | {2560, 1440}, 290 | {1366, 768}, 291 | {1440, 900}, 292 | {1536, 864}, 293 | {1680, 1050}, 294 | {1280, 1024}, 295 | {1280, 800}, 296 | {1280, 720}, 297 | {1600, 1200}, 298 | {1600, 900}, 299 | } 300 | 301 | type Fingerprint map[string]interface{} 302 | 303 | func randomScreenRes() []int { 304 | return screenRes[rand.Intn(len(screenRes))] 305 | } 306 | 307 | func getFingerprint() Fingerprint { 308 | f := make(Fingerprint) 309 | for k, v := range baseFingerprint { 310 | f[k] = v 311 | } 312 | f["DNT"] = "unknown" 313 | f["L"] = languages[rand.Intn(len(languages))] 314 | f["D"] = []int{1, 4, 8, 15, 16, 24, 32, 48}[rand.Intn(8)] 315 | f["PR"] = float64(rand.Intn(100))/100.0*2 + 0.5 316 | screenRes := randomScreenRes() 317 | f["S"] = screenRes 318 | f["AS"] = []int{screenRes[0], screenRes[1] - 40} 319 | f["TO"] = (rand.Intn(24) - 12) * 60 320 | f["SS"] = rand.Float64() > 0.5 321 | f["LS"] = rand.Float64() > 0.5 322 | f["IDB"] = rand.Float64() > 0.5 323 | f["B"] = rand.Float64() > 0.5 324 | f["ODB"] = rand.Float64() > 0.5 325 | f["CPUC"] = "unknown" 326 | f["PK"] = []string{"HP-UX", "Mac68K", "MacPPC", "SunOS", "Win16", "Win32", "WinCE"}[rand.Intn(7)] 327 | f["CFP"] = "" 328 | f["FR"] = false 329 | f["FOS"] = false 330 | f["FB"] = false 331 | f["JSF"] = "" 332 | if val, ok := f["P"].([]string); ok { 333 | newVal := make([]string, 0, len(val)) 334 | for _, v := range val { 335 | if rand.Float64() > 0.5 { 336 | newVal = append(newVal, v) 337 | } 338 | } 339 | f["P"] = newVal 340 | } 341 | f["T"] = []interface{}{rand.Intn(8), rand.Float64() > 0.5, rand.Float64() > 0.5} 342 | f["H"] = math.Pow(2, float64(rand.Intn(6))) 343 | 344 | return f 345 | } 346 | 347 | func prepareF(f Fingerprint) string { 348 | var res []string 349 | for key := range f { 350 | val := f[key] 351 | switch reflect.TypeOf(val).Kind() { 352 | case reflect.Slice: 353 | s := reflect.ValueOf(val) 354 | sliceOfString := make([]string, s.Len()) 355 | for i := 0; i < s.Len(); i++ { 356 | sliceOfString[i] = fmt.Sprintf("%v", s.Index(i).Interface()) 357 | } 358 | res = append(res, strings.Join(sliceOfString, ";")) 359 | default: 360 | res = append(res, fmt.Sprintf("%v", val)) 361 | } 362 | } 363 | return strings.Join(res, "~~~") 364 | } 365 | 366 | func prepareFe(fingerprint Fingerprint) []string { 367 | fe := make([]string, 0) 368 | for k, v := range fingerprint { 369 | switch k { 370 | case "P": 371 | value, ok := v.([]string) 372 | if ok { 373 | mapped := MapSlice(value, func(s string) string { 374 | split := strings.Split(s, "::") 375 | return split[0] 376 | }) 377 | fe = append(fe, fmt.Sprintf("%s:%s", k, strings.Join(mapped, ","))) 378 | } 379 | default: 380 | fe = append(fe, fmt.Sprintf("%s:%v", k, v)) 381 | } 382 | } 383 | return fe 384 | } 385 | 386 | func MapSlice(a []string, f func(string) string) []string { 387 | n := make([]string, len(a)) 388 | for i, e := range a { 389 | n[i] = f(e) 390 | } 391 | return n 392 | } 393 | -------------------------------------------------------------------------------- /fingerprint_test.go: -------------------------------------------------------------------------------- 1 | package funcaptcha 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGetFingerprint(t *testing.T) { 8 | fp := getFingerprint() 9 | println(fp) 10 | } 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/flyingpot/funcaptcha 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/bogdanfinn/fhttp v0.5.23 7 | github.com/bogdanfinn/tls-client v1.4.0 8 | ) 9 | 10 | require ( 11 | github.com/andybalholm/brotli v1.0.4 // indirect 12 | github.com/bogdanfinn/utls v1.5.16 // indirect 13 | github.com/klauspost/compress v1.15.12 // indirect 14 | github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 // indirect 15 | golang.org/x/crypto v0.1.0 // indirect 16 | golang.org/x/net v0.5.0 // indirect 17 | golang.org/x/sys v0.4.0 // indirect 18 | golang.org/x/text v0.6.0 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= 2 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 3 | github.com/bogdanfinn/fhttp v0.5.23 h1:4Xb5OjYArB8GpnUw4A4r5jmt8UW0/Cvey3R9nS2dC9U= 4 | github.com/bogdanfinn/fhttp v0.5.23/go.mod h1:brqi5woc5eSCVHdKYBV8aZLbO7HGqpwyDLeXW+fT18I= 5 | github.com/bogdanfinn/tls-client v1.4.0 h1:ptZmkvVyRTjMFPc3Kevholf+ioePkCM5oj3qkOmOuoM= 6 | github.com/bogdanfinn/tls-client v1.4.0/go.mod h1:lgtqsHjoJYQMPz6H08bc8t30bmUaYnVjwtfVEzMGJDs= 7 | github.com/bogdanfinn/utls v1.5.16 h1:NhhWkegEcYETBMj9nvgO4lwvc6NcLH+znrXzO3gnw4M= 8 | github.com/bogdanfinn/utls v1.5.16/go.mod h1:mHeRCi69cUiEyVBkKONB1cAbLjRcZnlJbGzttmiuK4o= 9 | github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= 10 | github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= 11 | github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 h1:YqAladjX7xpA6BM04leXMWAEjS0mTZ5kUU9KRBriQJc= 12 | github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5/go.mod h1:2JjD2zLQYH5HO74y5+aE3remJQvl6q4Sn6aWA2wD1Ng= 13 | golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= 14 | golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= 15 | golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= 16 | golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= 17 | golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= 18 | golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 19 | golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= 20 | golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 21 | -------------------------------------------------------------------------------- /murmur.go: -------------------------------------------------------------------------------- 1 | package funcaptcha 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | ) 7 | 8 | type digest struct { 9 | h1, h2 uint64 10 | length int 11 | seed uint64 12 | } 13 | 14 | func GetMurmur128String(input string, seed uint64) string { 15 | d := NewWithSeed(seed) 16 | d.Write([]byte(input)) 17 | h1, h2 := d.Sum() 18 | return fmt.Sprintf("%x%x", h1, h2) 19 | } 20 | 21 | func NewWithSeed(seed uint64) *digest { 22 | d := new(digest) 23 | d.seed = seed 24 | d.h1 = seed 25 | d.h2 = seed 26 | return d 27 | } 28 | 29 | func (d *digest) Write(data []byte) { 30 | length := len(data) 31 | d.length += length 32 | 33 | var ( 34 | h1 = d.h1 35 | h2 = d.h2 36 | c1 = uint64(0x87c37b91114253d5) 37 | c2 = uint64(0x4cf5ad432745937f) 38 | ) 39 | 40 | for len(data) >= 16 { 41 | k1 := binary.LittleEndian.Uint64(data) 42 | k2 := binary.LittleEndian.Uint64(data[8:]) 43 | 44 | k1 *= c1 45 | k1 = (k1 << 31) | (k1 >> (64 - 31)) 46 | k1 *= c2 47 | h1 ^= k1 48 | 49 | h1 = (h1 << 27) | (h1 >> (64 - 27)) 50 | h1 += h2 51 | h1 = h1*5 + 0x52dce729 52 | 53 | k2 *= c2 54 | k2 = (k2 << 33) | (k2 >> (64 - 33)) 55 | k2 *= c1 56 | h2 ^= k2 57 | 58 | h2 = (h2 << 31) | (h2 >> (64 - 31)) 59 | h2 += h1 60 | h2 = h2*5 + 0x38495ab5 61 | 62 | data = data[16:] 63 | } 64 | 65 | var k1, k2 uint64 66 | 67 | switch len(data) { 68 | case 15: 69 | k2 ^= uint64(data[14]) << 48 70 | fallthrough 71 | case 14: 72 | k2 ^= uint64(data[13]) << 40 73 | fallthrough 74 | case 13: 75 | k2 ^= uint64(data[12]) << 32 76 | fallthrough 77 | case 12: 78 | k2 ^= uint64(data[11]) << 24 79 | fallthrough 80 | case 11: 81 | k2 ^= uint64(data[10]) << 16 82 | fallthrough 83 | case 10: 84 | k2 ^= uint64(data[9]) << 8 85 | fallthrough 86 | case 9: 87 | k2 ^= uint64(data[8]) 88 | k2 *= c2 89 | k2 = (k2 << 33) | (k2 >> (64 - 33)) 90 | k2 *= c1 91 | h2 ^= k2 92 | 93 | fallthrough 94 | case 8: 95 | k1 ^= uint64(data[7]) << 56 96 | fallthrough 97 | case 7: 98 | k1 ^= uint64(data[6]) << 48 99 | fallthrough 100 | case 6: 101 | k1 ^= uint64(data[5]) << 40 102 | fallthrough 103 | case 5: 104 | k1 ^= uint64(data[4]) << 32 105 | fallthrough 106 | case 4: 107 | k1 ^= uint64(data[3]) << 24 108 | fallthrough 109 | case 3: 110 | k1 ^= uint64(data[2]) << 16 111 | fallthrough 112 | case 2: 113 | k1 ^= uint64(data[1]) << 8 114 | fallthrough 115 | case 1: 116 | k1 ^= uint64(data[0]) 117 | k1 *= c1 118 | k1 = (k1 << 31) | (k1 >> (64 - 31)) 119 | k1 *= c2 120 | h1 ^= k1 121 | } 122 | 123 | h1 ^= uint64(length) 124 | h2 ^= uint64(length) 125 | 126 | h1 += h2 127 | h2 += h1 128 | 129 | h1 = fmix(h1) 130 | h2 = fmix(h2) 131 | 132 | h1 += h2 133 | h2 += h1 134 | d.h1 = h1 135 | d.h2 = h2 136 | } 137 | 138 | func (d *digest) Sum() (h1, h2 uint64) { 139 | return d.h1, d.h2 140 | } 141 | 142 | func fmix(k uint64) uint64 { 143 | k ^= k >> 33 144 | k *= 0xff51afd7ed558ccd 145 | k ^= k >> 33 146 | k *= 0xc4ceb9fe1a85ec53 147 | k ^= k >> 33 148 | return k 149 | } 150 | -------------------------------------------------------------------------------- /murmur_test.go: -------------------------------------------------------------------------------- 1 | package funcaptcha 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGetMurmur128String(t *testing.T) { 8 | if GetMurmur128String("test", 31) != "ff55565a476832ed3409c64597508ca4" { 9 | t.Fatal("murmur error!") 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package funcaptcha 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "math" 7 | mathRand "math/rand" 8 | "strconv" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | const chars = "0123456789abcdef" 14 | const DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" 15 | 16 | type Bda struct { 17 | Key string `json:"key"` 18 | Value interface{} `json:"value"` 19 | } 20 | 21 | func Random() string { 22 | result := make([]byte, 32) 23 | for i := range result { 24 | result[i] = chars[mathRand.Intn(len(chars))] 25 | } 26 | return string(result) 27 | } 28 | 29 | func GetBda(userAgent string, referer string, location string) string { 30 | fp := getFingerprint() 31 | fe := prepareFe(fp) 32 | bda := []Bda{ 33 | {Key: "api_type", Value: "js"}, 34 | {Key: "p", Value: 1}, 35 | {Key: "f", Value: GetMurmur128String(prepareF(fp), 31)}, 36 | {Key: "n", Value: base64.StdEncoding.EncodeToString([]byte(strconv.Itoa(int(math.Round(float64(time.Now().Unix()))))))}, 37 | {Key: "wh", Value: Random() + "|" + Random()}, 38 | {Key: "enhanced_fp", Value: []Bda{ 39 | { 40 | Key: "webgl_extensions", 41 | Value: "ANGLE_instanced_arrays;EXT_blend_minmax;EXT_color_buffer_half_float;EXT_disjoint_timer_query;EXT_float_blend;EXT_frag_depth;EXT_shader_texture_lod;EXT_texture_compression_bptc;EXT_texture_compression_rgtc;EXT_texture_filter_anisotropic;EXT_sRGB;KHR_parallel_shader_compile;OES_element_index_uint;OES_fbo_render_mipmap;OES_standard_derivatives;OES_texture_float;OES_texture_float_linear;OES_texture_half_float;OES_texture_half_float_linear;OES_vertex_array_object;WEBGL_color_buffer_float;WEBGL_compressed_texture_s3tc;WEBGL_compressed_texture_s3tc_srgb;WEBGL_debug_renderer_info;WEBGL_debug_shaders;WEBGL_depth_texture;WEBGL_draw_buffers;WEBGL_lose_context;WEBGL_multi_draw", 42 | }, 43 | { 44 | Key: "webgl_extensions_hash", 45 | Value: Random(), 46 | }, 47 | { 48 | Key: "webgl_renderer", 49 | Value: "WebKit WebGL", 50 | }, 51 | { 52 | Key: "webgl_vendor", 53 | Value: "WebKit", 54 | }, 55 | { 56 | Key: "webgl_version", 57 | Value: "WebGL 1.0 (OpenGL ES 2.0 Chromium)", 58 | }, 59 | { 60 | Key: "webgl_shading_language_version", 61 | Value: "WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)", 62 | }, 63 | { 64 | Key: "webgl_aliased_line_width_range", 65 | Value: "[1, 1]", 66 | }, 67 | { 68 | Key: "webgl_aliased_point_size_range", 69 | Value: "[1, 1024]", 70 | }, 71 | { 72 | Key: "webgl_antialiasing", 73 | Value: "yes", 74 | }, 75 | { 76 | Key: "webgl_bits", 77 | Value: "8,8,24,8,8,0", 78 | }, 79 | { 80 | Key: "webgl_max_params", 81 | Value: "16,32,16384,1024,16384,16,16384,30,16,16,4095", 82 | }, 83 | { 84 | Key: "webgl_max_viewport_dims", 85 | Value: "[32767, 32767]", 86 | }, 87 | { 88 | Key: "webgl_unmasked_vendor", 89 | Value: "Google Inc. (NVIDIA)", 90 | }, 91 | { 92 | Key: "webgl_unmasked_renderer", 93 | Value: "ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Ti Direct3D11 vs_5_0 ps_5_0, D3D11)", 94 | }, 95 | { 96 | Key: "webgl_vsf_params", 97 | Value: "23,127,127,23,127,127,23,127,127", 98 | }, 99 | { 100 | Key: "webgl_vsi_params", 101 | Value: "0,31,30,0,31,30,0,31,30", 102 | }, 103 | { 104 | Key: "webgl_fsf_params", 105 | Value: "23,127,127,23,127,127,23,127,127", 106 | }, 107 | { 108 | Key: "webgl_fsi_params", 109 | Value: "0,31,30,0,31,30,0,31,30", 110 | }, 111 | { 112 | Key: "webgl_hash_webgl", 113 | Value: Random(), 114 | }, 115 | { 116 | Key: "user_agent_data_brands", 117 | Value: "Chromium,Google Chrome,Not:A-Brand", 118 | }, 119 | { 120 | Key: "user_agent_data_mobile", 121 | Value: false, 122 | }, 123 | { 124 | Key: "navigator_connection_downlink", 125 | Value: 10, 126 | }, 127 | { 128 | Key: "navigator_connection_downlink_max", 129 | Value: nil, 130 | }, 131 | { 132 | Key: "network_info_rtt", 133 | Value: 50, 134 | }, 135 | { 136 | Key: "network_info_save_data", 137 | Value: false, 138 | }, 139 | { 140 | Key: "network_info_rtt_type", 141 | Value: nil, 142 | }, 143 | { 144 | Key: "screen_pixel_depth", 145 | Value: 24, 146 | }, 147 | { 148 | Key: "navigator_device_memory", 149 | Value: 8, 150 | }, 151 | { 152 | Key: "navigator_languages", 153 | Value: "en-US,fr,fr-FR,en,nl", 154 | }, 155 | { 156 | Key: "window_inner_width", 157 | Value: 0, 158 | }, 159 | { 160 | Key: "window_inner_height", 161 | Value: 0, 162 | }, 163 | { 164 | Key: "window_outer_width", 165 | Value: 1920, 166 | }, 167 | { 168 | Key: "window_outer_height", 169 | Value: 1080, 170 | }, 171 | { 172 | Key: "browser_detection_firefox", 173 | Value: false, 174 | }, 175 | { 176 | Key: "browser_detection_brave", 177 | Value: false, 178 | }, 179 | { 180 | Key: "audio_codecs", 181 | Value: "{\"ogg\":\"probably\",\"mp3\":\"probably\",\"wav\":\"probably\",\"m4a\":\"maybe\",\"aac\":\"probably\"}", 182 | }, 183 | { 184 | Key: "video_codecs", 185 | Value: "{\"ogg\":\"probably\",\"h264\":\"probably\",\"webm\":\"probably\",\"mpeg4v\":\"\",\"mpeg4a\":\"\",\"theora\":\"\"}", 186 | }, 187 | { 188 | Key: "media_query_dark_mode", 189 | Value: true, 190 | }, 191 | { 192 | Key: "headless_browser_phantom", 193 | Value: false, 194 | }, 195 | { 196 | Key: "headless_browser_selenium", 197 | Value: false, 198 | }, 199 | { 200 | Key: "headless_browser_nightmare_js", 201 | Value: false, 202 | }, 203 | { 204 | Key: "window__ancestor_origins", 205 | Value: []string{}, 206 | }, 207 | { 208 | Key: "window__tree_index", 209 | Value: []string{}, 210 | }, 211 | { 212 | Key: "window__tree_structure", 213 | Value: "[[],[[]]]", 214 | }, 215 | { 216 | Key: "client_config__surl", 217 | Value: nil, 218 | }, 219 | { 220 | Key: "client_config__language", 221 | Value: nil, 222 | }, 223 | { 224 | Key: "navigator_battery_charging", 225 | Value: true, 226 | }, 227 | { 228 | Key: "audio_fingerprint", 229 | Value: "124.04347527516074", 230 | }, 231 | { 232 | Key: "mobile_sdk__is_sdk", 233 | Value: "__no_value_place_holder__", 234 | }, 235 | }}, 236 | {Key: "fe", Value: fe}, 237 | {Key: "ife_hash", Value: GetMurmur128String(strings.Join(fe, ", "), 38)}, 238 | {Key: "cs", Value: 1}, 239 | {Key: "jsbd", Value: "{\"HL\":4,\"DT\":\"\",\"NWD\":\"false\",\"DOTO\":1,\"DMTO\":1}"}, 240 | } 241 | 242 | var enhancedFp []Bda 243 | for _, val := range bda { 244 | if val.Key == "enhanced_fp" { 245 | enhancedFp, _ = val.Value.([]Bda) 246 | break 247 | } 248 | } 249 | 250 | if len(referer) > 0 { 251 | enhancedFp = append(enhancedFp, Bda{ 252 | Key: "document__referrer", 253 | Value: referer, 254 | }) 255 | } 256 | 257 | if len(location) > 0 { 258 | enhancedFp = append(enhancedFp, Bda{ 259 | Key: "window__location_href", 260 | Value: location, 261 | }, Bda{ 262 | Key: "client_config__sitedata_location_href", 263 | Value: location, 264 | }) 265 | } 266 | 267 | for i := range bda { 268 | if bda[i].Key == "enhanced_fp" { 269 | bda[i].Value = enhancedFp 270 | break 271 | } 272 | } 273 | 274 | b, _ := json.Marshal(bda) 275 | strB := string(b) 276 | strB = strings.ReplaceAll(strB, `,"value":"__no_value_place_holder__"`, "") 277 | currentTime := time.Now().Unix() 278 | key := userAgent + strconv.FormatInt(currentTime-(currentTime%21600), 10) 279 | ciphertext := Encrypt(strB, key) 280 | return base64.StdEncoding.EncodeToString([]byte(ciphertext)) 281 | } 282 | -------------------------------------------------------------------------------- /util_test.go: -------------------------------------------------------------------------------- 1 | package funcaptcha 2 | 3 | import "testing" 4 | 5 | func TestGetBda(t *testing.T) { 6 | res := GetBda("test-useragent", "test-referer", "test-location") 7 | println(res) 8 | } 9 | --------------------------------------------------------------------------------