{"id":30801941,"url":"https://github.com/implex-ltd/hcaptcha-reverse","last_synced_at":"2025-09-05T21:11:20.048Z","repository":{"id":206849696,"uuid":"691911879","full_name":"Implex-ltd/hcaptcha-reverse","owner":"Implex-ltd","description":"Reverse engineered hcaptcha.","archived":false,"fork":false,"pushed_at":"2025-04-14T12:05:53.000Z","size":29312,"stargazers_count":167,"open_issues_count":2,"forks_count":41,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-06-28T10:52:42.321Z","etag":null,"topics":["analysis","hcaptcha","reverse"],"latest_commit_sha":null,"homepage":"","language":"WebAssembly","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Implex-ltd.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-09-15T06:44:30.000Z","updated_at":"2025-06-27T06:31:22.000Z","dependencies_parsed_at":"2023-12-18T22:01:49.203Z","dependency_job_id":"8bf8d8db-29ca-4094-8045-54c969fcfbf1","html_url":"https://github.com/Implex-ltd/hcaptcha-reverse","commit_stats":null,"previous_names":["implex-ltd/hcaptcha-reverse"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Implex-ltd/hcaptcha-reverse","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Implex-ltd%2Fhcaptcha-reverse","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Implex-ltd%2Fhcaptcha-reverse/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Implex-ltd%2Fhcaptcha-reverse/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Implex-ltd%2Fhcaptcha-reverse/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Implex-ltd","download_url":"https://codeload.github.com/Implex-ltd/hcaptcha-reverse/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Implex-ltd%2Fhcaptcha-reverse/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273820972,"owners_count":25174125,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-05T02:00:09.113Z","response_time":402,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["analysis","hcaptcha","reverse"],"created_at":"2025-09-05T21:11:17.615Z","updated_at":"2025-09-05T21:11:20.033Z","avatar_url":"https://github.com/Implex-ltd.png","language":"WebAssembly","funding_links":[],"categories":[],"sub_categories":[],"readme":"# HCaptcha reverse engineered\n\n```\nthis repo (and more generally all those linked to implex) are the result of countless hours of work, a lot of learning and new things.\nTake the time to look and learn, instead of copying without thinking, because it won't work. If you need a developer, contact me.\n```\n\n```\n⚠️ PS: I see more and more people copying/stealing this script and reuploading it without credit on github, that's why I'm posting an update, be careful who you pay services to, especially towards those people who are just thieves and don't know how to do anything on their own.\n```\n\n## Hcaptcha update logs !\n\n- `1.40.16` They added \"obfuscation\" to the wasm, but hey!\n- `1.40.20?` They removed `version` params (*+ fixed rand?*) (now ima count 1.40.X)\n- `1.40.21` They renamed wasm bindgen binding name !!\n- `1.40.22` new param `ardata`\n- `1.40.23` dynamic numbers for `fingerprint_events` + fix `ardata` always null\n- `1.40.25` `fingerprint_events` order change ??\n- `1.60.0` New VM\n- `1.80.0?` New obfuscation + VM for motiondata which gets pushed into N data \n\n## String integrity check (outdated)\n\n[This script](https://gist.github.com/nikolahellatrigger/a8856463170fbe3596569977148ebaf4) is used to \"encode\" somes data into `fingerprint_event` field such as:\n    \n    - webgl vendor + renderer\n    - browser performance\n    - browser timezone\n    \nI think it's used to verify the data is authentic / non duplicated (output is different each time you run the function)\n\n## Lib used by WASM\n\n- https://crates.io/crates/rand_chacha/0.2.2 (encryption)\n- https://crates.io/crates/cipher/0.3.0 (encryption)\n- https://crates.io/crates/ctr/0.8.0 (encryption)\n- https://crates.io/crates/rust-hashcash/0.3.3 (stamp)\n- https://crates.io/crates/aes/0.7.5 (encryption)\n- https://crates.io/crates/js-sys/0.3.52 (javascript)\n- https://crates.io/crates/twox-hash/1.6.0 (hash)\n\n## Stamp (proof of work)\n\n[Hashcash](https://crates.io/crates/rust-hashcash/0.3.3) algorithm is used to generate stamp value as a POW with custom date format (`2006-01-02`), bits is set by using the difficulty present into the JWT \n\n## Fingerprint hash\n\n[XxHash3 (sixty_four.rs)](https://crates.io/crates/twox-hash/1.6.0) algorithm is used with custom seed (`5575352424011909552`) to create unique hash of 15 unique properties such as:\n\n    - Html DOM\n    - Webgl properties\n    - Css properties\n    - Javascript window functions\n    - ...\n    \n\n## Rand\n\nRand is a `CRC-32` checksum hash of the N payload in json format, it's used to check the payload integrity if you edited it from memory etc...\nFormat: `[math.random, crc-32 * 2.3283064365386963e-10]` (`table: 79764919`)\n\n## Encryptions\n\nThere are two encryptions in wasm `AES-256-GCM` (3 different keys)\n\nAnd one encryption in the JS `AES-128-CBC`\n\n- [N data encryption](https://github.com/Implex-ltd/hcaptcha-reverse/blob/main/encryptions/main.py)\n- [Request payload and response encryption](https://github.com/Implex-ltd/hcaptcha-reverse/blob/main/encryptions/request.py)\n- [Fingerprint blob encryption](https://github.com/Implex-ltd/hcaptcha-reverse/blob/main/encryptions/blob.py)\n\n- [AES key fetcher for N data encryption](https://github.com/Implex-ltd/hcaptcha-reverse/blob/main/encryptions/fetcher.py)\n\n## Fingerprint events\n\n\u003e `fingerprint_events` is parsed output of fingerprinting script, somes data are hashed.\n\u003e Final output is used into n data.\n\u003e Hash algorithm is xxHash3 (sixty_four.rs). \n\n### Raw javascript fp output (outdated)\n\n- You can use [fingerprint_dumper.js](https://github.com/Implex-ltd/hcaptcha-reverse/blob/main/versions/fingerprint_dumper.js) to to dump the current raw fp before they got parsed by WASM\n\n- [1.40.10](https://gist.github.com/nikolahellatrigger/65ff078faa990db653adb2d6052be6b0)\n- [1.39.0](https://gist.github.com/nikolahellatrigger/b34456fdc7383ffbb26246bb9db28b7e)\n\n| id     | type                                                                   | type      | hashed    | fp_raw                                                                              |\n| ------ | ---------------------------------------------------------------------- | --------- | --------- | ----------------------------------------------------------------------------------- |\n| `3`    |                                                                        | `float64` | **false** | [x](https://x.com)                                                                  |\n| `1902` | `57`                                                                   | `int`     | **false** | [x](https://x.com)                                                                  |\n| `1901` | math fingerprint wich give different result + err between device       | `u64`     | **true**  | [x](https://x.com)                                                                  |\n| `1101` | canvas fingerprint hash of the image (`data:image/png;base64,...`)     | `u64`     | **true**  | [x](https://x.com)                                                                  |\n| `1103` | `[255,255,255,255,192,192,192,255,244,244,244,255,53,53,53,255]`       | `array`   | **true**  | [x](https://x.com)                                                                  |\n| `1105` | `[14,4,1,41.3203125,17,4,44.2890625]`                                  | `array`   | **true**  | [x](https://x.com)                                                                  |\n| `1107` | `[274.609375,266,274.609375,266,274.609375,266,274.609375,....]`       | `array`   | **false** | [x](https://x.com)                                                                  |\n| `201`  | hash of `1107`                                                         |           | **false** | [x](https://x.com)                                                                  |\n| `211`  | audio fingerprint                                                      | `array`   | **true**  | [x](https://x.com)                                                                  |\n| `3401` | page HTML Tree                                                         |           | **true**  | [x](https://x.com)                                                                  |\n| `3403` | Link of hcaptcha.js                                                    |           | **false** | [x](https://x.com)                                                                  |\n| `803`  | `[1,4,5,7,9,12,20,21,24,25,29]`                                        | `array`   | **false** | [x](https://x.com)                                                                  |\n| `604`  | `[n.appv,n.ua,n.mem,n.hwconc,n.lang,n.langs,n.platform,n.cpu,versin]`  | `array`   | **false** | [link](https://gist.github.com/nikolahellatrigger/c4d6cf4ddb0ab219c38ddd133dc772eb) |\n| `2801` | probably webgl related                                                 | `u8`      | **true**  | [x](https://x.com)                                                                  |\n| `2805` | hash of `2801`                                                         | `array`   | **false** | [x](https://x.com)                                                                  |\n| `107`  | `[s.w,s.h,s.aw,s.ah,s.cd,s.pd,event,n.maxtp,w.dpr,w.ow,w.oh...]`       | `array`   | **false** | [link](https://gist.github.com/nikolahellatrigger/ea00832b010c0db8f0a0d5ca0d467072) |\n| `302`  | css default colors                                                     | `u64`     | **true**  | [x](https://x.com)                                                                  |\n| `303`  | `fonts`                                                                | `array`   | **false** | [x](https://x.com)                                                                  |\n| `301`  | CSS properties list                                                    | `u64`     | **true**  | [x](https://x.com)                                                                  |\n| `304`  | length of CSS properties                                               | `int`     | **false** | [x](https://x.com)                                                                  |\n| `1401` | `timezone`                                                             | `string`  | **false** | [x](https://x.com)                                                                  |\n| `1402` | `[timezone,x,x,new Date(\"1/1/1970\").getTimezoneOffset(),x,n.lang]`     | `array`   | **false** | [x](https://x.com)                                                                  |\n| `1403` | timezone \"encrypted\"                                                   | `array`   | **false** | [x](https://x.com)                                                                  |\n| `3504` |                                                                        | `float64` | **false** | [x](https://x.com)                                                                  |\n| `3501` | navigation timestamp                                                   | `array`   | **false** | [x](https://x.com)                                                                  |\n| `3503` | current unix timestamp                                                 | `int`     | **false** | [x](https://x.com)                                                                  |\n| `3502` |                                                                        | `float64` | **false** | [x](https://x.com)                                                                  |\n| `3505` |                                                                        | `float64` | **false** | [x](https://x.com)                                                                  |\n| `401`  | browser properties hash                                                | `u64`     | **true**  | [x](https://x.com)                                                                  |\n| `402`  | length of windows properties                                           | `int`     | **false** | [x](https://x.com)                                                                  |\n| `407`  | browser keys?                                                          | `array`   | **false** | [x](https://x.com)                                                                  |\n| `412`  | `[true,true,true,true,true,true,true,true,true,true,...]`              | `u64`     | **true**  | [x](https://x.com)                                                                  |\n| `2402` | `[webgl_vendor, webgl_renderer]`                                       | `array`   | **false** | [x](https://x.com)                                                                  |\n| `2420` | `[encrypt_webgl_vendor, encrypt_webgl_renderer]` \"encrypted\"           | `array`   | **false** | [x](https://x.com)                                                                  |\n| `2403` | `[webgl2_vendor, webgl2_renderer]`                                     | `array`   | **false** | [x](https://x.com)                                                                  |\n| `2401` | WebGL properties hash                                                  | `u64`     | **true**  | [x](https://x.com)                                                                  |\n| `2408` | `!!navigator.webdriver`                                                | `bool`    | **false** | [x](https://x.com)                                                                  |\n| `2407` | Math fingerprint of 28 first fibonacci number                          | `u64`     | **true**  | [x](https://x.com)                                                                  |\n| `2409` | probably webgl related                                                 | `array`   | **false** | [x](https://x.com)                                                                  |\n| `2410` | `[16,1024,4096,7,12,120,[23,127,127]]`                                 | `array`   | **false** | [x](https://x.com)                                                                  |\n| `2411` | `[32767,32767,16384,8,8,8]`                                            | `array`   | **false** | [x](https://x.com)                                                                  |\n| `2412` | `[1,1024,1,1,4]`                                                       | `array`   | **false** | [x](https://x.com)                                                                  |\n| `2413` | probably webgl related                                                 | `array`   | **false** | [x](https://x.com)                                                                  |\n| `2414` | `[16384,32,16384,2048,2,2048]`                                         | `array`   | **false** | [x](https://x.com)                                                                  |\n| `2415` | `[4,120,4]`                                                            | `array`   | **false** | [x](https://x.com)                                                                  |\n| `2416` | `[24,24,65536,212988,200704]`                                          | `array`   | **false** | [x](https://x.com)                                                                  |\n| `2417` | `[16,4095,30,16,16380,120,12,120,[23,127,127]]`                        | `array`   | **false** | [x](https://x.com)                                                                  |\n| `3800` | CSP (disabled)                                                         | `error`   | **false** | [x](https://x.com)                                                                  |\n| `1302` | `[0,1,2,3,4]`                                                          | `array`   | **false** | [x](https://x.com)                                                                  |\n| `901`  | All browser voices hash                                                | `u64`     | **true**  | [x](https://x.com)                                                                  |\n| `905`  | Browser voice enabled                                                  | `array`   | **false** | [x](https://x.com)                                                                  |\n| `3210` | `[143254600089,143254600089,null,null,4294705152,true,true,true,null]` | `array`   | **false** | [x](https://x.com)                                                                  |\n| `3211` | first arg of performance **3210** \"encrypted\"                          | `array`   | **false** | [x](https://x.com)                                                                  |\n| `702`  | `[os.name, os.version, null, os.bits, os.arch, navigator.version]`     | `array`   | **false** | [x](https://x.com)                                                                  |\n| `2001` | Permissions hash                                                       | `u64`     | **true**  | [x](https://x.com)                                                                  |\n| `2002` | Notifications permissions                                              | `array`   | **false** | [x](https://x.com)                                                                  |\n| `0`    |                                                                        | `float64` | **false** | [x](https://x.com)                                                                  |\n\n## Sandbox\n\nSandbox is fast way to encrypt own HSW without retrieving stuff as encryption-key, xxHash nonce and stuff that change, or if new update happen and you don't reversed it yet.\nYou can build custom HSW wasm using [builder](https://github.com/Implex-ltd/hcaptcha-reverse/blob/main/src/main.py) / [WABT](https://github.com/WebAssembly/wabt) tools.\n\nHSW usualy take less than 10ms to execute. (you have to remove all fingerprints from array)\n\n### how sandbox work ?\n\nthe sandbox executes a custom hsw containing a hand-modified WASM which adds the payload to be encrypted to the end of memory and returns the pointer to encrypt our payload and not the one generated by hcaptcha. It's kinda smart isn't it ?\n\n### Wasm hook\n```wasm\n;; new modules import\n(func $./client_bg.js.inject (;31;) (import \"./client_bg.js\" \"inject\") (param i32 i32))\n(func $./client_bg.js.getLen (;56;) (import \"./client_bg.js\" \"getLen\") (result i32))\n(func $./client_bg.js.getPtr (;75;) (import \"./client_bg.js\" \"getPtr\") (result i32))\n\n;; edited function: func 150 (1.39) // func 152 (1.40.10)\n\n;; JSON is built above...\nlocal.set $var7\nlocal.get $var5\ni32.const 32\ni32.add\n\nlocal.get $var6 ;; load len of the JSON\nlocal.get $var7 ;; load ptr of the JSON\ncall $./client_bg.js.inject ;; append custom payload into memory\n      \ncall $./client_bg.js.getLen\nlocal.set $var6 ;; ^+ get the payload len and overwrite original one\n      \ncall $./client_bg.js.getPtr\nlocal.set $var7 ;; ^+ get the payload ptr and overwrite original one\n\ncall $func211 ;; continue wasm with out custom payload...\n```\n\n### Hsw hook\n```js\n\nlet jlen = 0\nlet jptr = 0\nlet fp_json_curr = {}\n\n// this append over and over and can lead to memory leak // 100% RAM but it's working\nfunction appendJsonToMemory(pp) {\n    const to_inject = new TextEncoder().encode(pp);\n    const buffer = M.memory.buffer;\n\n    const currentSize = buffer.byteLength;\n    const requiredSize = currentSize + to_inject.length;\n\n    M.memory.grow(Math.ceil((requiredSize - currentSize) / 65536));\n\n    const updatedBuffer = M.memory.buffer;\n    const memoryView = new Uint8Array(updatedBuffer);\n\n    memoryView.set(to_inject, currentSize);\n\n    return {\n        ptr: currentSize,\n        len: to_inject.length\n    };\n}\n\ninject: function (len, ptr) {\n    try {\n        /*\n            - This part was used to get the stamp + rand when it was not fully reversed\n\n            let parsed = JSON.parse(__getStrFromWasm(ptr, len))\n            fp_json_curr.stamp = parsed.stamp\n            fp_json_curr.rand = parsed.rand\n        */\n\n        console.log(JSON.stringify(fp_json_curr))\n        const data = appendJsonToMemory(JSON.stringify(fp_json_curr));\n\n        // save new ptr + len\n        jlen = data.len\n        jptr = data.ptr\n        } catch (err) { console.log(err) }\n},\n\ngetPtr: function () {\n    return jptr\n},\n\ngetLen: function () {\n    return jlen\n},\n```\n\n## Key Building Algo\n\nthe 32 byte key is generated as follows:\n\n1. the first 2 bytes are taken directly from the `key_seed` (in little format)\n2. the remaining 30 bytes are generated iteratively:\n\n   for each step (0-29):\n   \n   a. if not the first step, update the seed using an LCG:\n      ```\n      seed = (seed * 6364136223846793005) \u0026 0xFFFFFFFFFFFFFFFF\n      seed = (seed ± key_factor1) \u0026 0xFFFFFFFFFFFFFFFF  // + or - depending on operator\n      ```\n   \n   b. calculate memory access positions:\n      ```\n      base_index = memory + step\n      memory_position = base_index + key_factor2\n      segment_address = (((memory_position // 320) \u003c\u003c 3) + memory_position + 1032 - 1075552) % len(memory)\n      mask_address = (memory_position % 96) + 8\n      ```\n   \n   c. extract values from memory:\n      ```\n      segment_value = 32 bit little value from memory[segment_address]\n      mask_value = 64 bit little value from memory[mask_address]\n      ```\n   \n   d. calculate hash value:\n      ```\n      hash_value = (segment_value ^ (mask_value \u0026 0xFFFFFFFF)) \u0026 0xFF\n      ```\n   \n   e. extract and process bit positions from the seed:\n      ```\n      bit45 = (seed \u003e\u003e 45) \u0026 0xFFFFFFFF\n      bit27 = (seed \u003e\u003e 27) \u0026 0xFFFFFFFF\n      bit59 = (seed \u003e\u003e 59) \u0026 0xFFFFFFFF\n\n      if bit45 \u0026 0x80000000: bit45 = bit45 - 0x100000000\n      if bit27 \u0026 0x80000000: bit27 = bit27 - 0x100000000\n      if bit59 \u0026 0x80000000: bit59 = bit59 - 0x100000000\n      ```\n   \n   f. combine and rotate bits:\n      ```\n      combined = bit45 ^ bit27\n      shift = bit59 % 32\n      rotated = ((combined \u003e\u003e shift) | (combined \u003c\u003c (32 - shift))) \u0026 0xFFFFFFFF\n\n      if rotated \u0026 0x80000000: rotated = rotated - 0x100000000\n      ```\n   \n   g. calculate final key byte and add to key:\n      ```\n      key_byte = (hash_value ^ rotated) \u0026 0xFF\n      key_bytes.append(key_byte)\n      ```\n\n3. The final key is the hex representation of all 32 bytes\n\n### Linear Congruential Generator\n\nthe algorithm uses an LCG with the following parameters:\n- multiplier: 6364136223846793005\n- increment: different based on `key_factor1` and `operator`\n- modulus: 2^64\n\n## Notes (from Cyrus)\n\n- This community is a shitty community filled with arrogant people *ehm ehm dort* who can't take anything seriously\n- This is going to be the last time you are seeing me, good luck to everyone who is on this path (The VM is pretty fun). \n- If you urgently need me contact me (Cyrus) at telegram: @hcaptcha_staff\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimplex-ltd%2Fhcaptcha-reverse","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fimplex-ltd%2Fhcaptcha-reverse","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimplex-ltd%2Fhcaptcha-reverse/lists"}