├── .gitignore ├── .npmignore ├── .gitattributes ├── assets └── encryption.jpg ├── src ├── index.js ├── encryption.js └── decryption.js ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── CONTRIBUTING.md ├── package.json ├── LICENSE ├── tests ├── validity_check.js ├── excepted.txt └── original.json ├── README.md └── docs ├── LEARN.md ├── ENCRYPTION.md └── technical_analysis.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | data/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | tests/ 2 | docs/ 3 | data/ 4 | assets/ -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /assets/encryption.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glizzykingdreko/datadome-encryption/HEAD/assets/encryption.jpg -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { DataDomeEncryptor } = require('./encryption'); 2 | const { DataDomeDecryptor } = require('./decryption'); 3 | 4 | module.exports = { 5 | DataDomeEncryptor, 6 | DataDomeDecryptor 7 | }; -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Pull Request Template 2 | 3 | ## Description 4 | _Describe your changes and the motivation behind them._ 5 | 6 | ## Related Issues 7 | _Reference any related issues (e.g., Fixes #123)_ 8 | 9 | ## Checklist 10 | - [ ] My code follows the project style and guidelines 11 | - [ ] I have added tests where applicable 12 | - [ ] All tests pass (`npm test`) 13 | - [ ] I have updated documentation as needed 14 | 15 | ## Additional Notes 16 | _Anything else reviewers should know?_ -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Issue Template 2 | 3 | ## Type of Issue 4 | - [ ] Bug Report 5 | - [ ] Feature Request 6 | - [ ] Question 7 | 8 | ## Description 9 | 10 | _Describe your issue or request in detail._ 11 | 12 | ## Steps to Reproduce (for bugs) 13 | 1. 14 | 2. 15 | 3. 16 | 17 | ## Expected Behavior 18 | 19 | _What did you expect to happen?_ 20 | 21 | ## Actual Behavior 22 | 23 | _What actually happened?_ 24 | 25 | ## Additional Information 26 | 27 | _Any other context, logs, or screenshots?_ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to datadome-encryption 2 | 3 | Thank you for your interest in contributing! 4 | 5 | ## How to Contribute 6 | 7 | - **Bug Reports & Feature Requests:** 8 | - Please use [GitHub Issues](https://github.com/glizzykingdreko/datadome-encryption/issues) to report bugs or request features. 9 | 10 | - **Pull Requests:** 11 | - Fork the repository and create your branch from `main`. 12 | - Add tests for new features or bug fixes if possible. 13 | - Ensure your code passes existing tests (`npm test`). 14 | - Open a pull request with a clear description of your changes. 15 | 16 | ## Code Style 17 | 18 | - Use clear, descriptive variable and function names. 19 | - Add comments where necessary, especially for complex logic. 20 | - Keep code modular and avoid duplication. 21 | 22 | ## Community 23 | 24 | - Be respectful and constructive in discussions. 25 | - All contributions are welcome! -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "datadome-encryption", 3 | "version": "1.1.1", 4 | "description": "DataDome encryption and decryption utilities for Node.js", 5 | "main": "src/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/glizzykingdreko/datadome-encryption.git" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/glizzykingdreko/datadome-encryption/issues" 12 | }, 13 | "homepage": "https://github.com/glizzykingdreko/datadome-encryption#readme", 14 | "author": "GlizzyKingDreko ", 15 | "license": "MIT", 16 | "keywords": [ 17 | "datadome", 18 | "encryption", 19 | "decryption", 20 | "captcha", 21 | "interstitial", 22 | "takionapi" 23 | ], 24 | "files": [ 25 | "src/", 26 | "README.md", 27 | "LICENSE" 28 | ], 29 | "scripts": { 30 | "test": "node tests/validity_check.js", 31 | "prepare": "echo 'Preparing for npm publish'" 32 | } 33 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 GlizzyKingDreko 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 | -------------------------------------------------------------------------------- /tests/validity_check.js: -------------------------------------------------------------------------------- 1 | const { DataDomeDecryptor, DataDomeEncryptor } = require('../src'); 2 | const fs = require('fs'); 3 | 4 | console.log('=============================='); 5 | console.log(' DataDome Encryption Test Suite'); 6 | console.log('==============================\n'); 7 | 8 | const cid = "k6~sz7a9PBeHLjcxOOWjR162xQq2Uxsx6wLzxeGlO7~6k3JVwDkwAaQ04wdFEMm2Jt2s0y61mLfJdhWuqtqeJzFMuo7Lf8P5btYX0K4EeoLRcNAtNW04rGhTE3nKpMxi" 9 | const hash_str = "14D062F60A4BDE8CE8647DFC720349" 10 | const expected = fs.readFileSync(__dirname + '/excepted.txt', 'utf8'); 11 | const original_signals = JSON.parse(fs.readFileSync(__dirname + '/original.json', 'utf8')); 12 | 13 | console.log('[1] Encrypting signals...'); 14 | const encryptor = new DataDomeEncryptor( 15 | hash_str, 16 | cid, 17 | salt=null, // generate it 18 | challengeType='captcha' 19 | ); 20 | 21 | for (let i = 0; i < original_signals.length; i++) { 22 | let key = original_signals[i][0]; 23 | let value = original_signals[i][1]; 24 | encryptor.add(key, value); 25 | } 26 | let newEncrypted = encryptor.encrypt(); 27 | console.log(' > Encrypted string:', newEncrypted); 28 | 29 | // We ignore the last char on compilation due to the 30 | // fact that is salt based, so unless you pass 31 | // the same salt it will be a different char based 32 | // on the timestamp 33 | let decryptedData_wo_last_char = newEncrypted.slice(0, -1); 34 | let expected_wo_last_char = expected.slice(0, -1); 35 | 36 | if (decryptedData_wo_last_char === expected_wo_last_char) { 37 | console.log('[PASS] Encryption matches expected (ignoring last char).'); 38 | } else { 39 | console.log('[FAIL] Encryption does NOT match expected (ignoring last char).'); 40 | console.log(' > Expected:', expected_wo_last_char); 41 | console.log(' > Got :', decryptedData_wo_last_char); 42 | } 43 | 44 | console.log('\n[2] Decrypting and comparing data...'); 45 | const decryptor = new DataDomeDecryptor( 46 | hash_str, 47 | cid, 48 | salt=null, // generate it 49 | challengeType='captcha' 50 | ); 51 | 52 | let originalData = decryptor.decrypt(expected); 53 | let rebuiltData = decryptor.decrypt(newEncrypted); 54 | 55 | let mismatch = false; 56 | for (let i = 0; i < rebuiltData.length; i++) { 57 | const [rebuildKey, rebuildValue] = rebuiltData[i]; 58 | const [originalKey, originalValue] = originalData[i]; 59 | 60 | if (rebuildKey !== originalKey || rebuildValue !== originalValue) { 61 | mismatch = true; 62 | console.log(`\n[MISMATCH] Entry #${i}`); 63 | console.log(` (ORIGINAL) Key: ${originalKey}`); 64 | console.log(` (ORIGINAL) Value: ${originalValue}`); 65 | console.log(` (REBUILD ) Key: ${rebuildKey}`); 66 | console.log(` (REBUILD ) Value: ${rebuildValue}`); 67 | console.log("********************"); 68 | } 69 | } 70 | 71 | if (!mismatch) { 72 | console.log('[PASS] Decryption: All entries match!'); 73 | } else { 74 | console.log('[FAIL] Decryption: There were mismatches in the decrypted data.'); 75 | } 76 | 77 | console.log('\n=============================='); 78 | console.log(' Test Suite Complete'); 79 | console.log('=============================='); -------------------------------------------------------------------------------- /tests/excepted.txt: -------------------------------------------------------------------------------- 1 | hzKpi7eFaeSHLyWT4nv-b-zFfUs_G0N3cSU7BJFbDlXZXYr5nppRySAiE-e89Zn5vzH10PFXwpJdDuoUetLBYa0oGGzNmrXfutowTIRAf0EzSDcR2mJ3_DLiuAZ-qb5mTLg_-LBpnSPe_mF1JZjfThBWEvaGMd64IFpHt_9SK5eaI1kcWxKCCYYd7Hq-uX4GeNCYBsl14F_0A8QmVfAykApKx8HF0oKj13hji1b4Wcx-voIrn2Z8g68CPQB5ZYNOgUv1tcYfZsVSMJ-Uih5kgcq73dpJz3JpFVpt0vr8GA3iL5RmfNtdi8ACko1ymA_dFmtQId47rImCbPLvNBykqRyNs3f9r2gmJoEwmMvxp1GRvXuV6Lb9w4kPVME5nUiVWvQ7z7odvH2kCPYRUjH-l_RvJaScHTlgEgD-VOriBjKzmlq-Fcq9g91ffdLdQrHn3Ls8Z1y03Kn8tgE8hVC0-nBk6CMOqaKk_63UPpuxneAfwmzFJr_9TvM9FYo31S30qh1zgHO7zUB6CIRQp3GgniTOecuVTrZQdowE26OHxxBs0uUf_roibwat5Pg4cURW-eChEmTIsmaixGqzaEntqHqHmf6phGJrySbM2lJp6KbDPPr0V5X8nI0HlmT9iUqpatDPCYUrWu240Un1bJnLzWRtWn_QTrWJaRsuF4Rexdll81Ic-AX3jYQlMc7SBuykxvcOpecUthEqhcXfLvsN7QECKdkFQ5ygrjfJH4Rn0PzoT3WPIBpaBYoT79mzMWbnyIcS6o-9ugsxevVtE6GGYPAVJlqhwd_M6dMOvm3OyX6YXw39QtfGfPxWKeMzdlAFuHLoX8YlIxA-EDwkyYDAjaOduwsmipncqEvn0WGVLwlY_Szbx59POapLKJmk0ZU9CFGWxpSyjbsq849CeIeVtE0v2-PnbZldD_sZkLfiYtaEfJ7bD7AX-iYezBiB1d7i4hk-LbgFhoYZQ0B8711HHD8fgMrDkn3fcwjX8owGhxyV32nc8L_uCeYLzR16a7ax8NJPeuwhL8mwKPcQ1N4CUF2kE4s3f4Neae7wxFOyEpEiq8Lv2PVorq86LW0Jn1FdNrgXxwy-RRC3nP2LqmsyO121d4M-W0ZSkYgNR9rdzOsl4UWHBzbMeB3omoFfOfkisk6BSq0okcUou_KymUON_eUPyhBw2b8PEW8V3c_bdvzR5XKK40J7wz7wOlvWPDGmDuFu63i3NPiAD9vl13Fv5tZOALYI40kxKVvaVuJV07T9y8L0pirEmkPeQy67u7HsiDx0W72wkYmFROMYcV7Lv4fgnjTl_4-jx9dNGuUtaK5_hGsmglvKQqOWKaRqvVu06XizUMAHCfjt6C7y0oED-4vIOZ6e9svO14DEzfmN9Jhlg5aGZvSAZcHg5Gs3qPP-56n4v87kSaXdbxUIqv3cbda9VC8JLPNvsxom0xw3PRrBN2pdcbtce5Jh5dnucNlf1ycC1f97cDhac0o88MjVasj7Kmxj6DXhJIXTOOhC3SYtSCmWXxgwOEjhvmyWHiQy5sq-HRpTod7W17Yx8OmX2yBBeX0YcK0nSsj5R2gOvIMs7JpHA_HavTgNjdnewdq2R403jkQKjxOWOJj44zOCw_bBi6TIv5Em1N1RLBYsGWTsnjmI0mYGw5KMj2tbBO2zEWvRCVTyrdoVL54egP2YcwJENNaDvcwEl67gZFwg8QqQo6ROXw_6OaNbTDgMzifxEboB88P7EX-atyy2gFoqjSr79_bhTfelQCESRQnOJdcEGgKYGUPrzsucUwCaATC_X6eYSLxXMHEYNtVjmNf8N3D-JnsGB0jVifhvpUzACw-HZYCH8b7DywfTanH7WUh59tLi24sR05gBww1eBVACciDMstJ-gDTeUaP2cIXkfxkRLiwGyWwfRKGRlFWHQ0YbCEtrTQVkFpeLGt3NpKkC54XeT5JYqmnYL9TsYv2Ec-YHlqKfYi-HFdq3j6yqj8HVjBlh6lJgKmWtxLKpGPojIbTJJ5oVvLq5-snGPETrUy1-gR4EHELPuNru0_FOl3J1IaF6DblbwBv_jJVUL0l8wCVyH0M8V6f7DK794Apf2RYBu6UhTtsZmF2jVmEI5vMrhwSNxFaXe9sfeYZ_fpEHB9R1c5qxVu83WeYZmA4P5H003Ebom8M1c5ltc8a_h7Q52p4fKR9sf0fTco5fr-Q5XX8h8fu3dkr4MxvStXeKlmsMcTd8_EHVCOm2-WbzGBNda3a5idOQKNoviS5K6u3JJRm5FPdR0snXeAEFbn_dqmPC1UEbpEX7U7ngbRI-ozpM1Ie12HwqivfXO8YWaUxddH7PD7dmcMD_0ejAl7FIFVD1eQFGq4Z1Aks6S-Tv36zRlY2tlIT8Dpnc8PnhxV9Y5H7SOzyHcF6rkBpOcj9OE1zLzbLS6Zx6tOka2itWmKLKlt1wL9LCfZojOyNb7ILKdhg_gMVuVE78c15jrirHCv4yeRjhdQnYJHfWDrBy1rWsjbUP_hFJk1YqQ8AngqGUdRbxutskECXK8kv2OnQLaqoYr7zN_JLizhz1iLEXZMV-nacXWgxOM3wR6ddUGeiTrWKcuFjzYCtKaEVwIOFbBDjvKWt5HWm40cgPYoUkp5uTJ3bl54Jay6s90LKboosx8F-HRrqsMCeHn8HVybKHq83MB4W9PfKXL911l7gml4J9g8pA1Sp9oVp1zeIenZhvEbLQfcz4QfV53kcYUuB-XCHAf09yRhc7b1JQ8rYIRmqXKNki5GsbPRfVrBEh6_iS4pJJAYW0-ahx-KdD0_Qo05YV_buoZMcLJTp3-stY4-vymHZmnuoMW9vMR0Di_5ZRYPutnaIcJFc7lcLQvVmowg-uCpdzbFD3_cxcffCcBPM5I-I2c9TIKSjRUkH13TBZKSMLF7_Iqh9Mz4-O-e9leVKQkdadpJYEQNWhKa8_Oa2djOnonPkWLvb6D3TW7S-LaRJbcq4AqTWr8MiBgAtC57xfI34pBOHVYzcsQW6QKwZsLabavFVq5mxlpEOi0B0u1HHUSItzp-u1xNfOESP4AeZKThoGeVqgf6K4j3gSnwXZB-ZEP0B4rwO_rjQ90_Yv1Hb2lCGQzaOGUVTD-NMzfh8RI08Uk6UbMsqGXmyNtb3CLey92dONrBi2i2g5jwbmjEp3sa5e3qJKodN3XAxA-NVeoxqdZ1D2rrrUhpvmpj4KnC2WfCOsVtAX8FrL7iJZyDWPrPc24ivQ3sj9ZVS_7Vk7961eJwm64h0swI6KBEXWjKdGk1pctShKc4Qwgw3VMwnqvHvOqaS0M6Eh5lhBR_hU_el4NKZTqWx3LTZ6t4EzLHPgz8c7183hEINnWH1TMSrSFn8hm5D4sltaTr9TqeG1i6G6LNQ748KE-FJFe3KP8cU1tbPFbTy-k86ey1ou4jvtprbzCuCDXBwdRwaEvqfVhP2TT0TRLvSa8EJf5ccNvDcSc3FtiI0LjkOtH-_gVMDN_Q7B36Ue2RfiHbgS_Qa3rkhhic0_zzsNrwWs0g_rUZimsK6slPPRFprHKqLyqN2dzGG2C9VfbQUDsl-Ha3FJgXUxoZ3n8dcGFo5_ejkPJMSH7sxnhq9n7ChCNjVW-sY0Py77y5xEt1XSE_boOg-2t-ppPSdEqZE1CZkUwWr_zKpjjqJ1z_hAEYLqVl4TW4rUb9owjKHiWh8vjPtrRLgci_bpnNbl0qnxw7oU8497Ttl9um_mZoiFgIxyDl7nhnuc_h4XcxKsIE7XH4fZLTP7r-T6KDyTb-8U5IbnXoP276YFmEWS1Jlh9t56goEZRKftBWTIBMyg2CR5_nMcKX-TJULOQez4hUqTvYFQqc4gFozKjnplRHOQLcaxtuu2v_RUT1Q7bmxO9BUK3riqrFsV2Ubyv5igxwQId0pTgFRQt8NYYpEqAPIghBrLWJUJqNyON5tnrL5Q4-HwAQ8O5akbP-mRn8YHQw5avH-Yw1N0oBIdElHI4o8FoDIRRNthWTRDC0fUgOr3bz_eIoO9jM1i0rN_HMHy_b8X8JxpmngFWXMcuWv_Qf8-nfAt4QBV59g9ekx0Uoq0q5Y-3u_lYFvXf-WEqOAyVWABsjlQFOPoMfvgViC-bOuLxWF9AeHq1_5txEemMU6EpoDnOMjp19A9hl-mJQaz_ItQp1rwIozm7UBGwr8kmlrxwBx8Et_YI7JWGfnyMG5JZ6istGjLFGomQaf-cAQ1yEekYbLfmjejbqpwkcXUnxCeICsccyxVoAcUwghuQizKHCdAB033pwoR2JLh9xHtauRgvTRi_FNg2t3aBFz6XLlawYWiQeDMPZvVLRStmkd9zxRR8fXrMN_aW7qhjCAljivp5Oz4q8Q1RfbibvWSvrd6B_fzRfhZKwpyaOWyXSx30-K1jfAGfJHA7sScldP7bHtO4NrgABzy-CagflF1B3KkEBN0WiKa26EeVHM0CvQWGsV9vbZm6gDTBfqnErnBPasHngAWb_O16tSynFUhI4Wkn9trdxMHEtELf_i4piqilpfugN3zBtzxz1SDgSpexES9_E_CjrwmPECBX1zQb0GM0Z69cNK49U2O3uZ8N95e4ZmMt6qNW8oZb_z5R7q1MbKD3w9ezt7trAKOi4qyjfVQDm-BO5Myw5afmSXxfhYMT2ws01yd2D0lbfmr8UJ0XtLXaJC7LReCGl8BMV-7MF_pY4CyAlF1xirGJcIHF272K7hX7AgajFCMPjqWtFObqY9bPFxnL3lAYC067Hj4bKJR9nCTX9fb1D0t15ozoIz_IufF61IsushUZPxgrXeCyJ1aZ5hqfQQjWZRXuS-9HVqveCeroNAclUbLg2L4pGm4CWH3Y5EQ0mLLwW-fUkUq9LZodUnF1WdRlaE4FBwCwj1dVGNuuhmwmD05vQdHdNyV0IJKhdKXj8URD272S1XR3dQz0WftvGPqJF62boExvei_k30WkwhgXQLQqY13f_Tf9PFLX_dIJPOGJI8JtYdaY8A2mHio6c8K_lzy2E3bIvTPq0j76kXDvMBnCbrPJmjcAsSRD47QCrN_7QPSOqTPfQHDkU2k0R3hLLM21kb3u4w_gnQE2NHMj1GewvvO4GDyQSGcb57TeJk63yYe8dpkGhGQEpJqdMtw1sXxCApEhrGpl3ckymZg1cSKfK8gtBh4Z-w9rUJP-ANQiEyOnwQaGdTe2gBmP6UhbwtQb8wyOTpk3Tai-MqVJW21Em_7Og1RbHgyrefnISwc0P7Kzep2Mztb5U3BLE8f4qrjYsfP_JZCODkgYETwmXL-C58RTo5rQhI8mwHWkQsH_Cfmq028m_LLKjn-ERsH7ZHIy0vKd8uAOXqX16FpchhYC2j57WirJsvtGoL5FFwyJyAlQ2JSkF9QF_RrzcG6_x3bfoWK1Yvm3rCVnBHjWL17RhZyKwsu48_iCQRiT-ADM4HY1uH_CPPkvEUpODoO9FRA6Yrm5p3oohrICImtRZJxKS2Wm17bIWOjNwrOcfvlSsqW5HB3CGrLkxo6sW0CVgah6HKhunKDDsVttMVU3hhxaMaxxP23tCcDVwAdXc3kFXDmT8Ao5SqFsgAU378wc0aiHmiDtSANCgSRdLS1gkr0SXs-OIJNpdP_aAYcFyimum_MSl2rN39MSuuy-wr6KQLAsjrJkF7KDbe3F_5HZFQpyw_F95vUyn1P5E5Nkf7jv_Dtl6hiL-1cZVS4zfvTJyb44nAyaOToiuz-ECcWa77uPSQgXa93hkZyG7kdkyZYhklaMsb-trErpXr3MQ_QTy1n5q69Xc-d_WX5_AYZsOG6ExH5e0Fg3icWSD3DgUvw4XJqwjkrIzjFHd45gKafS4JC6MaqJalN_1QH_FUz_AgmudUXEqIycvRfnp3hBA_i5ckH3ZT3mLA7pce5zEioCXdibPcbAFirlFNL1sTgcVHBNYCLVP1T_aDrRcOnkPKSryUsTO8IEC4joSDglA89yQ2k0f4RrMO89gZG_gMCRyetHqDtUe6Gm3353nHhmvj1e4-4Lfwtp5kNXMozOTf4ry3N7grWOc5v5bkjlzWJDIPO0Rd7hwjejczeny4LM_iaLVk04he2Ph8JWiYmtzz4_kFBeHAjcuuuXucQjm7Mwj4iyrN9VqnapZtwGuyHUkNjf4z01URZsG6ID0z6tFbfb1UEhKT6SIyzZpGFQV9jzUs_pS1XHanwbfamf-vRwQo7JZRMBc1SXDVjR9ldKxtXoH8Z9iyts92zdFVzR1_XqtErCryXs0aqQf0IGxdfPIKFusTJ_Sf7JBgvXkrtsG0jgqlBkQD-GZwp8qFdUCq62Xp0fprCj_r3d70cf32Wf7kmKoYq9xvWRCnW2XPkixprBYM2eftKd2STdqPSfIfWrVq90gpUCZ3UWfzZ9_FjMQs027hRXGUAo1iK256k3KqGaFYHhmlWK9G0zWRM9aORPojtw5_iJ5TAaErJG3GwGdODByXe79xUqr-F6YqvqOcEdHZE4-9hqIrOpBMDQjjBzM7SBrjOLkQpRbhpl3S1mVlpWaNjVqOYvK2QsYzQlyjsZ68NbyHNeJ_b4P3NvteFVvkvHc2hIh3LaYp0Qjk8cBAveVlxf-S7liPoKLgAq3yt9oc7k6H_RcE-cJl_fBDzy6p9DwGcn7WkEkYC8x3jLvc3BNHF4jLBl77Xz45Wi-IalVBDAH5y9nPzr7-SIELBsZGp6cKrfI3F0fnZaVifwKVYXMqjIsZZszjHMsv-Y_RdcMag9KTxIBGghoCnIN5bHfZ-qHZ-GxGhmbTe21KX09kN1jZUZ4zDmJBMvo59GfJ59JTsbvGMq3hEQWyf-w3ZnV4Ur1WJnxLxjpJ1S8soyPDO61bPxADhFDbwHQs1zn21_w6_706hr1ozTPu6kG1qofifWDApLpVVkGtpSA_U7jRUTTxzogjlAsJrP-ZJbqPxVekbxEaGG2ZP_16iGmhR3KXvlFqOYAIsM9p1By6iTrN6WfPyBvKBWtWzOWq2z8lFN8s4UyuOUTQJeYrX8-V1praytE1A1dO_0tYCB23BQ16ARiCIA8IIsOvjMNHHuoB6ER_iCBkieJD-NYepy_LcYU8bTQ1rusfwkIALVg8trhFaxqLSb3dxwIGYLUgaQULyYMJfl-_6Hxmf6C0OzD8vXukH0UNEcRUXbNcqb9ssfvUR8GBe5G8c0wi6nD_dAx4oxj38oGIZVxbs5mYSymdQWmKSxBFY57jqyPoZYmBB-UFdAamKyb_cmVBlGNHZXs6nWzJy3c9ycCLdnDbtLhLTCV9ghu5UIPiufGR2OvxWyzn9eL -------------------------------------------------------------------------------- /src/encryption.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | /** 4 | * DataDomeEncryptor 5 | * Implements a custom encryption/obfuscation routine for key-value data pairs. 6 | * The encryption logic is intentionally complex and mimics a real-world obfuscated payload builder. 7 | */ 8 | class DataDomeEncryptor { 9 | /** 10 | * @param {string} hash - The hash string used as part of the encryption seed. 11 | * @param {string} cid - The client/session identifier used in the payload. 12 | * @param {number|null} salt - Optional external salt for the encryption process. 13 | * @param {string} challengeType - Type of challenge: 'captcha' or 'interstitial' (default: 'captcha') 14 | */ 15 | constructor(hash, cid, salt = null, challengeType = 'captcha') { 16 | this.hash = hash; 17 | this.cid = cid; 18 | this.challengeType = challengeType.toLowerCase(); 19 | 20 | // Set correct constants based on challenge type 21 | if (this.challengeType === 'interstitial') { 22 | // Interstitial challenge constants 23 | this._mainPrngConstant = 9959949970; 24 | this._hashXorConstant = -883841716; // Interstitial uses a different hash XOR constant 25 | this._cidPrngConstant = 1809053797; 26 | this._hsv = this._generateHsv(); 27 | } else { 28 | // Default captcha challenge constants 29 | this._mainPrngConstant = 9959949970; 30 | this._hashXorConstant = -1748112727; 31 | this._cidPrngConstant = 1809053797; 32 | this._hsv = this._generateHsv(); 33 | } 34 | 35 | this._externalSalt = salt; // Store the externally provided salt 36 | this._initEncryptor(); 37 | } 38 | 39 | /** 40 | * Generates a pseudo-random HSV string based on the hash and random values. 41 | * Used as a hidden value in the encryption process. 42 | * @returns {string} 43 | */ 44 | _generateHsv() { 45 | const last4 = this.hash.slice(-4); 46 | const randIndex = Math.floor(Math.random() * 9); 47 | const randHex = Math.random().toString(16).slice(2, 10).toUpperCase(); 48 | return randHex.slice(0, randIndex) + last4 + randHex.slice(randIndex); 49 | } 50 | 51 | /** 52 | * Hashes a string using a custom algorithm, returns a 32-bit integer or a fallback constant. 53 | * @param {string} str 54 | * @returns {number} 55 | */ 56 | _customHash(str) { 57 | if (!str) return 1789537805; 58 | let hash = 0; 59 | for (let i = 0; i < str.length; i++) { 60 | hash = (hash << 5) - hash + str.charCodeAt(i) | 0; 61 | } 62 | return hash !== 0 ? hash : 1789537805; 63 | } 64 | 65 | /** 66 | * Encodes a 6-bit value into a custom character code for the payload. 67 | * @param {number} value 68 | * @returns {number} 69 | */ 70 | _encode6Bits(value) { 71 | if (value > 37) { 72 | return 59 + value; 73 | } else if (value > 11) { 74 | return 53 + value; 75 | } else if (value > 1) { 76 | return 46 + value; 77 | } else { 78 | return 50 * value + 45; 79 | } 80 | } 81 | 82 | /** 83 | * Bitwise mixing function for PRNG state. 84 | * @param {number} value 85 | * @returns {number} 86 | */ 87 | _mixInt(value) { 88 | value ^= value << 13; 89 | value ^= value >> 17; 90 | return value ^ value << 5; 91 | } 92 | 93 | /** 94 | * Creates a PRNG function with internal state, used for obfuscation. 95 | * @param {number} seed 96 | * @param {number} salt 97 | * @returns {function(boolean): number} 98 | */ 99 | _createPrng(seed, salt) { 100 | let state = seed, round = -1, saltState = salt, useAlt = this._useAlt; 101 | this._useAlt = false; 102 | let cache = null; 103 | return [function (flag) { 104 | let result; 105 | if (cache !== null) { 106 | result = cache; 107 | cache = null; 108 | } else { 109 | if (++round > 2) { 110 | state = DataDomeEncryptor.prototype._mixInt(state); 111 | round = 0; 112 | } 113 | result = state >> (16 - 8 * round); 114 | if (useAlt) { 115 | result ^= --saltState; 116 | } 117 | result &= 255; 118 | if (flag) { 119 | cache = result; 120 | } 121 | } 122 | return result; 123 | }]; 124 | } 125 | 126 | /** 127 | * Converts a string to a UTF-8 byte array and XORs each byte with the PRNG. 128 | * @param {string} str 129 | * @param {function(): number} prng 130 | * @returns {number[]} 131 | */ 132 | _utf8Xor(str, prng) { 133 | let utf8Bytes = []; 134 | let idx = 0; 135 | for (let i = 0; i < str.length; i++) { 136 | let code = str.charCodeAt(i); 137 | if (code < 128) { 138 | utf8Bytes[idx++] = code; 139 | } else if (code < 2048) { 140 | utf8Bytes[idx++] = code >> 6 | 192; 141 | utf8Bytes[idx++] = 63 & code | 128; 142 | } else if (55296 == (64512 & code) && i + 1 < str.length && 56320 == (64512 & str.charCodeAt(i + 1))) { 143 | // Surrogate pair 144 | code = 65536 + ((1023 & code) << 10) + (1023 & str.charCodeAt(++i)); 145 | utf8Bytes[idx++] = code >> 18 | 240; 146 | utf8Bytes[idx++] = code >> 12 & 63 | 128; 147 | utf8Bytes[idx++] = code >> 6 & 63 | 128; 148 | utf8Bytes[idx++] = 63 & code | 128; 149 | } else { 150 | utf8Bytes[idx++] = code >> 12 | 224; 151 | utf8Bytes[idx++] = code >> 6 & 63 | 128; 152 | utf8Bytes[idx++] = 63 & code | 128; 153 | } 154 | } 155 | // XOR each byte with prng() 156 | for (let j = 0; j < utf8Bytes.length; j++) { 157 | utf8Bytes[j] ^= prng(); 158 | } 159 | return utf8Bytes; 160 | } 161 | 162 | /** 163 | * Safely JSON-stringifies a value, returns undefined on error. 164 | * @param {any} value 165 | * @returns {string|undefined} 166 | */ 167 | _safeJson(value) { 168 | try { 169 | if (typeof value === 'string') { 170 | // Ensure string escaping is consistent 171 | return JSON.stringify(value); 172 | } 173 | return JSON.stringify(value); 174 | } catch (e) { 175 | return; 176 | } 177 | } 178 | 179 | /** 180 | * Encodes an array of bytes into a custom base64-like string. 181 | * @param {number[]} byteArr 182 | * @param {number} salt 183 | * @param {function(number): number} encode6Bits 184 | * @returns {string} 185 | */ 186 | _encodePayload(byteArr, salt, encode6Bits) { 187 | let i = 0; 188 | let output = []; 189 | let n = salt; 190 | // Process each group of 3 bytes 191 | while (i < byteArr.length) { 192 | // Combine 3 bytes into a 24-bit number, with obfuscation 193 | let chunk = (255 & --n ^ byteArr[i++]) << 16 | 194 | (255 & --n ^ byteArr[i++]) << 8 | 195 | (255 & --n ^ byteArr[i++]); 196 | // Split into 4 groups of 6 bits and encode 197 | output.push( 198 | String.fromCharCode(encode6Bits((chunk >> 18) & 63)), 199 | String.fromCharCode(encode6Bits((chunk >> 12) & 63)), 200 | String.fromCharCode(encode6Bits((chunk >> 6) & 63)), 201 | String.fromCharCode(encode6Bits(chunk & 63)) 202 | ); 203 | } 204 | // Handle padding if input length is not a multiple of 3 205 | let mod = byteArr.length % 3; 206 | if (mod) output.length -= 3 - mod; 207 | return output.join(''); 208 | } 209 | 210 | /** 211 | * Initializes or resets the encryption state (PRNG, buffer, etc.). 212 | */ 213 | _resetEncryptionState() { 214 | this._useAlt = true; 215 | this._prngSeed = this._mainPrngConstant ^ this._customHash(this.hash) ^ this._hashXorConstant; 216 | if (this._externalSalt !== null && this._externalSalt !== undefined) { 217 | this._salt = this._externalSalt; 218 | } else { 219 | this._salt = this._mixInt(this._mixInt((Date.now() >> 3) ^ 11027890091) * this._mainPrngConstant); 220 | } 221 | this.salt = this._salt; // Expose the salt used 222 | this._prng = this._createPrng(this._prngSeed, this._salt)[0]; 223 | this._buffer = []; 224 | this._isFirst = true; 225 | this._seenKeys = new Set(); 226 | 227 | // Expose seed values for testing/debugging (without changing encryption logic) 228 | this.prngSeed = this._prngSeed; 229 | this.cidPrngSeed = this._cidPrngConstant ^ this._customHash(this.cid); 230 | } 231 | 232 | /** 233 | * Initializes the encryption engine and sets up the addSignal and buildPayload methods. 234 | */ 235 | _initEncryptor() { 236 | this._resetEncryptionState(); 237 | this.addSignal = this._addSignal.bind(this); 238 | this.buildPayload = this._buildPayload.bind(this); 239 | } 240 | 241 | /** 242 | * Adds a key-value pair to the buffer, obfuscated and encoded. 243 | * @param {string} key 244 | * @param {string|number|boolean} value 245 | */ 246 | _addSignal(key, value) { 247 | const allowedTypes = ['number', 'string', 'boolean']; 248 | if (typeof key === 'string' && key.length !== 0 && (!value || allowedTypes.includes(typeof value))) { 249 | let hsvTemp; 250 | const keyStr = this._safeJson(key); 251 | const valueStr = this._safeJson(value); 252 | if (key && valueStr !== undefined && key !== 'xt1') { 253 | const startByte = this._prng() ^ (this._buffer.length ? 44 : 123); 254 | this._buffer.push(startByte); 255 | const keyBytes = this._utf8Xor(keyStr, this._prng); 256 | Array.prototype.push.apply(this._buffer, keyBytes); 257 | const sepByte = 58 ^ this._prng(); 258 | this._buffer.push(sepByte); 259 | const valueBytes = this._utf8Xor(valueStr, this._prng); 260 | Array.prototype.push.apply(this._buffer, valueBytes); 261 | if (this._isFirst) { 262 | this._isFirst = false; 263 | if ((typeof this._hsv === 'string' && this._hsv.length > 0) || 264 | (typeof this._hsv === 'number' && !isNaN(this._hsv))) { 265 | hsvTemp = this._hsv; 266 | } 267 | } 268 | } 269 | } 270 | } 271 | 272 | /** 273 | * Builds the final encrypted payload string for a given cid. 274 | * @param {string} cid 275 | * @returns {string} 276 | */ 277 | _buildPayload(cid) { 278 | const cidPrng = this._createPrng(this._cidPrngConstant ^ this._customHash(cid), this._salt)[0]; 279 | // Write buffer before cidPrng XOR 280 | // fs.writeFileSync('debug_encrypt_buffer.json', JSON.stringify(this._buffer)); 281 | let output = []; 282 | for (let i = 0; i < this._buffer.length; i++) output.push(this._buffer[i] ^ cidPrng()); 283 | output.push(125 ^ this._prng(true) ^ cidPrng()); 284 | const encoded = this._encodePayload(output, this._salt, this._encode6Bits.bind(this)); 285 | return encoded; 286 | } 287 | 288 | /** 289 | * Adds a key-value pair to the encryption buffer (public method). 290 | * @param {string} key 291 | * @param {string|number|boolean} value 292 | */ 293 | add(key, value) { 294 | this.addSignal(key, value); 295 | } 296 | 297 | /** 298 | * Builds the encrypted payload for the current cid (public method). 299 | * @returns {string} 300 | */ 301 | encrypt() { 302 | return this.buildPayload(this.cid); 303 | } 304 | 305 | /** 306 | * Checks if the encrypted result matches the expected output. 307 | * @param {string} encrypted 308 | * @param {string} exceptedPath 309 | * @returns {boolean} 310 | */ 311 | static checkResult(encrypted, exceptedPath) { 312 | const excepted = fs.readFileSync(exceptedPath, 'utf-8'); 313 | const isCorrect = encrypted === excepted; 314 | return isCorrect; 315 | } 316 | 317 | /** 318 | * Gets the challenge type currently being used 319 | * @returns {string} 320 | */ 321 | getChallengeType() { 322 | return this.challengeType; 323 | } 324 | 325 | /** 326 | * Updates the challenge type 327 | * @param {string} challengeType - 'captcha' or 'interstitial' 328 | */ 329 | setChallengeType(challengeType) { 330 | this.challengeType = challengeType.toLowerCase(); 331 | 332 | // Set the correct constants based on challenge type 333 | this._mainPrngConstant = 9959949970; 334 | this._cidPrngConstant = 1809053797; 335 | 336 | if (this.challengeType === 'interstitial') { 337 | this._hashXorConstant = -883841716; 338 | } else { 339 | this._hashXorConstant = -1748112727; 340 | this._hsv = this._generateHsv(); 341 | } 342 | 343 | this._initEncryptor(); 344 | } 345 | } 346 | 347 | // Export the DataDomeEncryptor class 348 | module.exports = { DataDomeEncryptor }; 349 | 350 | -------------------------------------------------------------------------------- /tests/original.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | "iaG6RD", 4 | "1.16.2" 5 | ], 6 | [ 7 | "TSm4y3", 8 | "2D726A03490F" 9 | ], 10 | [ 11 | "22Zb3S", 12 | "29bdd18e49c487be9b3f7021fbe0fbe0b5b484c886ca9840ad051de25451b527" 13 | ], 14 | [ 15 | "PUuTxz", 16 | 0 17 | ], 18 | [ 19 | "J61tIH", 20 | "bgra8unorm" 21 | ], 22 | [ 23 | "ahijox", 24 | "packed_4x8_integer_dot_product,unrestricted_pointer_parameters,pointer_composite_access,readonly_and_readwrite_storage_textures" 25 | ], 26 | [ 27 | "tBmLbP", 28 | "NA" 29 | ], 30 | [ 31 | "zMWDvj", 32 | "NA" 33 | ], 34 | [ 35 | "zfc4FD", 36 | "8c7284cfd638d38a2481f9763357f7f0fe052613944475eb3c63975a15cab5a4" 37 | ], 38 | [ 39 | "4ghAQR", 40 | false 41 | ], 42 | [ 43 | "2lsTCG", 44 | false 45 | ], 46 | [ 47 | "axYVG6", 48 | false 49 | ], 50 | [ 51 | "5pH2d1", 52 | "debug" 53 | ], 54 | [ 55 | "HJiVZn", 56 | 0 57 | ], 58 | [ 59 | "I1zUDu", 60 | 0 61 | ], 62 | [ 63 | "8spYKG", 64 | 0 65 | ], 66 | [ 67 | "2eBzbq", 68 | 0 69 | ], 70 | [ 71 | "0ZJ9w4", 72 | false 73 | ], 74 | [ 75 | "vL9rAl", 76 | true 77 | ], 78 | [ 79 | "1DryCr", 80 | true 81 | ], 82 | [ 83 | "sPUOr3", 84 | true 85 | ], 86 | [ 87 | "9Ri2bV", 88 | true 89 | ], 90 | [ 91 | "udGbaz", 92 | true 93 | ], 94 | [ 95 | "xv10IN", 96 | true 97 | ], 98 | [ 99 | "wzYJl8", 100 | true 101 | ], 102 | [ 103 | "u2vcC6", 104 | true 105 | ], 106 | [ 107 | "DYKRZY", 108 | true 109 | ], 110 | [ 111 | "UjOGAz", 112 | true 113 | ], 114 | [ 115 | "tFXxcG", 116 | true 117 | ], 118 | [ 119 | "vnyKUw", 120 | true 121 | ], 122 | [ 123 | "NSY4CP", 124 | false 125 | ], 126 | [ 127 | "S0RGbC", 128 | false 129 | ], 130 | [ 131 | "73d34a", 132 | true 133 | ], 134 | [ 135 | "Q74TQZ", 136 | "679201794" 137 | ], 138 | [ 139 | "3AFFyC", 140 | "probably" 141 | ], 142 | [ 143 | "7Ed5hQ", 144 | false 145 | ], 146 | [ 147 | "erTsL5", 148 | "probably" 149 | ], 150 | [ 151 | "1NW18m", 152 | true 153 | ], 154 | [ 155 | "ZQppDl", 156 | "probably" 157 | ], 158 | [ 159 | "jPJlZe", 160 | false 161 | ], 162 | [ 163 | "jrypVn", 164 | "maybe" 165 | ], 166 | [ 167 | "MJCsG6", 168 | false 169 | ], 170 | [ 171 | "pJlrEx", 172 | "probably" 173 | ], 174 | [ 175 | "7vQged", 176 | true 177 | ], 178 | [ 179 | "KcZnDd", 180 | "" 181 | ], 182 | [ 183 | "i15yk3", 184 | false 185 | ], 186 | [ 187 | "T8tuQc", 188 | "probably" 189 | ], 190 | [ 191 | "ThkFbu", 192 | false 193 | ], 194 | [ 195 | "7AIw2L", 196 | "maybe" 197 | ], 198 | [ 199 | "JaQqyf", 200 | false 201 | ], 202 | [ 203 | "oiIZPo", 204 | "probably" 205 | ], 206 | [ 207 | "6tmqgM", 208 | false 209 | ], 210 | [ 211 | "vIHkvq", 212 | "maybe" 213 | ], 214 | [ 215 | "I2hwB9", 216 | false 217 | ], 218 | [ 219 | "2gLPE6", 220 | false 221 | ], 222 | [ 223 | "KurKFS", 224 | "8cd0430a734f37ef54830838cd948e905f3aabf9af9a2852fe7d2b6d34c60796_1920_1055_8" 225 | ], 226 | [ 227 | "f8PHBc", 228 | 2874258578 229 | ], 230 | [ 231 | "aTJPJW", 232 | "Error\nat na (https://geo.captcha-delivery.com/captcha/?initialCid=AHrlqAAAAAMAy4YBSLL3Z4MAyV2i2w==&cid=k6~sz7a9PBeHLjcxOOWjRBogX_Lyb1shEWeUq12V7i7m6dr" 233 | ], 234 | [ 235 | "K9fGzk", 236 | "%2F&hash=14D062F60A4BDE8CE8647DFC720349&t=fe&s=44330&e=8cd0430a734f37ef54830838cd948e9012f1962596c8f19028ea8264361768fb&ir=414%2C3259&dm=dc_ir:649:728" 237 | ], 238 | [ 239 | "CnizH2", 240 | 1960513229 241 | ], 242 | [ 243 | "Z5wS9K", 244 | "79747f85b9969cae08cd98c4187036b37bcb5be66feadc44ef3ba1c7668ff08a" 245 | ], 246 | [ 247 | "2ARH5O", 248 | ",1,3,4,6,7,8,23," 249 | ], 250 | [ 251 | "tdoLuD", 252 | "America/Sao_Paulo" 253 | ], 254 | [ 255 | "jNp5AR", 256 | 2253.7000000029802 257 | ], 258 | [ 259 | "rt70De", 260 | 0 261 | ], 262 | [ 263 | "aD5Svk", 264 | 0 265 | ], 266 | [ 267 | "bh4R1L", 268 | -2263.2999999970198 269 | ], 270 | [ 271 | "iKrnd1", 272 | 679.5 273 | ], 274 | [ 275 | "P5iRwT", 276 | 46.29999999701977 277 | ], 278 | [ 279 | "cDN4Yj", 280 | 2954.2000000029802 281 | ], 282 | [ 283 | "aEBSM3", 284 | 4.899999998509884 285 | ], 286 | [ 287 | "3gnmCF", 288 | 0 289 | ], 290 | [ 291 | "uKVCRV", 292 | "http/1.1" 293 | ], 294 | [ 295 | "ixiyg0", 296 | 0 297 | ], 298 | [ 299 | "SYMmJy", 300 | "navigation" 301 | ], 302 | [ 303 | "zNtWdL", 304 | 0.09999999403953552 305 | ], 306 | [ 307 | "CFzMZw", 308 | 2207.5 309 | ], 310 | [ 311 | "HeVWgK", 312 | 46.78138527830304 313 | ], 314 | [ 315 | "EMTmLF", 316 | 0 317 | ], 318 | [ 319 | "MI9kAf", 320 | 0 321 | ], 322 | [ 323 | "FAi14W", 324 | 0 325 | ], 326 | [ 327 | "VSvYTH", 328 | 0 329 | ], 330 | [ 331 | "JYbuSo", 332 | 0.7814595542588232 333 | ], 334 | [ 335 | "4u1FjR", 336 | false 337 | ], 338 | [ 339 | "GCwzKz", 340 | false 341 | ], 342 | [ 343 | "GXM6qL", 344 | false 345 | ], 346 | [ 347 | "Xv8Doj", 348 | false 349 | ], 350 | [ 351 | "2csTjv", 352 | false 353 | ], 354 | [ 355 | "S4ORbo", 356 | false 357 | ], 358 | [ 359 | "P9VaT9", 360 | true 361 | ], 362 | [ 363 | "jKiSUr", 364 | "en-GB" 365 | ], 366 | [ 367 | "tbeEVz", 368 | false 369 | ], 370 | [ 371 | "uyljmm", 372 | true 373 | ], 374 | [ 375 | "DynOFK", 376 | 50 377 | ], 378 | [ 379 | "oI5mpq", 380 | "Google Inc." 381 | ], 382 | [ 383 | "L7iy93", 384 | "NA" 385 | ], 386 | [ 387 | "jYVG7b", 388 | "defined" 389 | ], 390 | [ 391 | "wWS1OK", 392 | false 393 | ], 394 | [ 395 | "X1MGwY", 396 | false 397 | ], 398 | [ 399 | "rjKNRo", 400 | false 401 | ], 402 | [ 403 | "DlPnVm", 404 | false 405 | ], 406 | [ 407 | "yPg9wn", 408 | false 409 | ], 410 | [ 411 | "1z85iD", 412 | false 413 | ], 414 | [ 415 | "TTn8MB", 416 | false 417 | ], 418 | [ 419 | "J9oEys", 420 | "10.24,5.22,6.23,1.20,8.57,9.36,13.21,8.19,3.21" 421 | ], 422 | [ 423 | "DHMRwP", 424 | "38, 0, 10" 425 | ], 426 | [ 427 | "jlcygS", 428 | "1.19527, 0.0852896, -0.0636875, 0.00482116, 0.412295, 1.11902, 9.23642, -0.699199, 0.678284, -8.48481, 1.36708, -0.103488, 8.96013, -112.084, 18.0592, -0.367082" 429 | ], 430 | [ 431 | "n46bcr", 432 | "15px" 433 | ], 434 | [ 435 | "nWihpo", 436 | 180 437 | ], 438 | [ 439 | "9YmZJc", 440 | false 441 | ], 442 | [ 443 | "bT87Bn", 444 | true 445 | ], 446 | [ 447 | "xmYO0l", 448 | true 449 | ], 450 | [ 451 | "wk5k2h", 452 | 40 453 | ], 454 | [ 455 | "O1xeSh", 456 | ",loadTimes,csi,app" 457 | ], 458 | [ 459 | "JdW0R8", 460 | false 461 | ], 462 | [ 463 | "l0GauR", 464 | "PDF Viewer,Chrome PDF Viewer,Chromium PDF Viewer,Microsoft Edge PDF Viewer,WebKit built-in PDF" 465 | ], 466 | [ 467 | "Ol0TiY", 468 | false 469 | ], 470 | [ 471 | "B5WfJd", 472 | 5 473 | ], 474 | [ 475 | "9wG2z9", 476 | true 477 | ], 478 | [ 479 | "9uVHfX", 480 | true 481 | ], 482 | [ 483 | "KqyrqI", 484 | false 485 | ], 486 | [ 487 | "UmbLvM", 488 | false 489 | ], 490 | [ 491 | "lKhsio", 492 | false 493 | ], 494 | [ 495 | "05Unpx", 496 | "" 497 | ], 498 | [ 499 | "Mwr0Xm", 500 | 950 501 | ], 502 | [ 503 | "HpJ1o2", 504 | 934 505 | ], 506 | [ 507 | "XHdRCZ", 508 | 950 509 | ], 510 | [ 511 | "4yJVaK", 512 | 934 513 | ], 514 | [ 515 | "WhsT1H", 516 | 1920 517 | ], 518 | [ 519 | "fZtTPz", 520 | 1055 521 | ], 522 | [ 523 | "lnXH9w", 524 | 1920 525 | ], 526 | [ 527 | "c97HrM", 528 | 1080 529 | ], 530 | [ 531 | "BaWdeA", 532 | 24 533 | ], 534 | [ 535 | "a8cGoy", 536 | 1 537 | ], 538 | [ 539 | "THcttY", 540 | "landscape-primary" 541 | ], 542 | [ 543 | "i65Gdm", 544 | "application/pdf,text/pdf" 545 | ], 546 | [ 547 | "qDOfBG", 548 | "3223aeb6721e0d0917e7928181193ac88dcd62fad5cadfbe7a2b2b473ecf58ee70f098dbdb1a1832e8dc6528387b0745971dbcd82384261e9a4e3f" 549 | ], 550 | [ 551 | "DdDaFW", 552 | "" 553 | ], 554 | [ 555 | "zY19mL", 556 | false 557 | ], 558 | [ 559 | "9EJrkO", 560 | "probably" 561 | ], 562 | [ 563 | "RMfzWD", 564 | true 565 | ], 566 | [ 567 | "IaQe0Z", 568 | "probably" 569 | ], 570 | [ 571 | "YLgfbn", 572 | true 573 | ], 574 | [ 575 | "09uvdb", 576 | "maybe" 577 | ], 578 | [ 579 | "Jh5giW", 580 | false 581 | ], 582 | [ 583 | "NpVIRv", 584 | "" 585 | ], 586 | [ 587 | "p3F7WM", 588 | false 589 | ], 590 | [ 591 | "Cl6XGC", 592 | "" 593 | ], 594 | [ 595 | "m1Aa5g", 596 | false 597 | ], 598 | [ 599 | "L6wWd0", 600 | "probably" 601 | ], 602 | [ 603 | "btm6yt", 604 | true 605 | ], 606 | [ 607 | "G4U4Gw", 608 | "MacIntel" 609 | ], 610 | [ 611 | "pomZFk", 612 | 8 613 | ], 614 | [ 615 | "LRIlJ2", 616 | 1055 617 | ], 618 | [ 619 | "56Ckh6", 620 | 1920 621 | ], 622 | [ 623 | "rxaWPi", 624 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36" 625 | ], 626 | [ 627 | "JWMfLJ", 628 | false 629 | ], 630 | [ 631 | "QCQ56f", 632 | 0 633 | ], 634 | [ 635 | "PGeYTK", 636 | false 637 | ], 638 | [ 639 | "oOdscd", 640 | "[\"en-GB\",\"en-US\",\"en\"]" 641 | ], 642 | [ 643 | "uoWw76", 644 | 8 645 | ], 646 | [ 647 | "Pckheq", 648 | "aptr:fine, ahvr:hover" 649 | ], 650 | [ 651 | "MUErAR", 652 | 2165063239 653 | ], 654 | [ 655 | "70DTzt", 656 | "79747f85b9969cae08cd98c4187036b37bcb5be66feadc44ef3ba1c7668ff08a" 657 | ], 658 | [ 659 | "shq4kH", 660 | 7 661 | ], 662 | [ 663 | "QrqDqg", 664 | 4 665 | ], 666 | [ 667 | "PLWeDl", 668 | "55dd7a53161c9dc1133591cbab2f0dc3" 669 | ], 670 | [ 671 | "PUuTxz", 672 | 123930350726998 673 | ], 674 | [ 675 | "N4qYlY", 676 | 0.10000000149011612 677 | ], 678 | [ 679 | "mICbhK", 680 | 50 681 | ], 682 | [ 683 | "TbcDV5", 684 | "\\kg20va 6 | Status: Complete 7 | Type: Research 8 | License: MIT 9 | npm version 10 | GitHub stars 11 | GitHub repo 12 | 13 | 14 |
15 | 16 |
17 | Check the Python version 18 | Read the full article on Medium 19 |
20 |
21 | 22 | 23 | ## Documentation 24 | 25 | This repository contains a complete analysis of DataDome's encryption system: 26 | 27 | | Document | Description | 28 | |----------|-------------| 29 | | [README.md](README.md) | Project overview and introduction | 30 | | [ENCRYPTION.md](./docs/ENCRYPTION.md) | Detailed analysis of the encryption algorithm | 31 | | [DECRYPTION.md](./docs/DECRYPTION.md) | Implementation of the decryption process | 32 | | [technical_analysis.md](./docs/technical_analysis.md) | Technical deep-dive into cryptographic properties | 33 | | [LEARN.md](./docs/LEARN.md) | Educational guide on reverse engineering techniques | 34 | 35 | ## Table of Contents 36 | - [DataDome Encryption System: Reverse Engineering \& Implementation](#datadome-encryption-system-reverse-engineering--implementation) 37 | - [Documentation](#documentation) 38 | - [Table of Contents](#table-of-contents) 39 | - [Installation \& Quick Start](#installation--quick-start) 40 | - [Basic Usage Example](#basic-usage-example) 41 | - [Project Overview](#project-overview) 42 | - [Reverse Engineering Process](#reverse-engineering-process) 43 | - [Step 1: Original Code Extraction](#step-1-original-code-extraction) 44 | - [Step 2: Core Function Identification](#step-2-core-function-identification) 45 | - [Step 3: Algorithmic Analysis](#step-3-algorithmic-analysis) 46 | - [Step 4: Rewriting as a Clean Implementation](#step-4-rewriting-as-a-clean-implementation) 47 | - [Decryption Implementation](#decryption-implementation) 48 | - [Usage Examples](#usage-examples) 49 | - [Encryption](#encryption) 50 | - [Different Challenge Types](#different-challenge-types) 51 | - [Technical differences between challenge types:](#technical-differences-between-challenge-types) 52 | - [Decryption](#decryption) 53 | - [Technical Deep Dive](#technical-deep-dive) 54 | - [Project Files](#project-files) 55 | - [Learn from this Project](#learn-from-this-project) 56 | - [Conclusion](#conclusion) 57 | - [Critical Evaluation of DataDome's Encryption](#critical-evaluation-of-datadomes-encryption) 58 | - [Author](#author) 59 | - [Contributing](#contributing) 60 | 61 | --- 62 | 63 | **Need DataDome Bypass Solutions?** 64 | 65 | If you need a reliable DataDome bypass solution for your project, turn to the experts who truly understand the technology. My company, TakionAPI, offers professional anti-bot bypass APIs with proven effectiveness against DataDome and other bot-defense systems. 66 | 67 | No more worrying about understanding, reversing, and solving the challenge yourself, or about keeping it up to date every day. One simple API call does it all. 68 | 69 | We provide free trials, example implementations, and setup assistance to make the entire process easy and smooth. 70 | - 📄 [Check our straightforward documentation](https://docs.takionapi.tech) 71 | - 🚀 [Start your trial](https://dashboard.takionapi.tech) 72 | - 💬 [Contact us on Discord](https://takionapi.tech/discord) for custom development and support. 73 | 74 | **Visit [TakionAPI.tech](https://takionapi.tech) for real, high-quality anti-bot bypass solutions — we know what we're doing.** 75 | 76 | --- 77 | 78 | ## Installation & Quick Start 79 | 80 | Install the module from npm: 81 | 82 | ```bash 83 | npm install datadome-encryption 84 | ``` 85 | 86 | ### Basic Usage Example 87 | 88 | ```js 89 | const { DataDomeEncryptor, DataDomeDecryptor } = require('datadome-encryption'); 90 | 91 | const cid = "YOUR_CLIENT_ID"; 92 | const hash = "YOUR_HASH_STRING"; 93 | const signals = [ 94 | ["key1", "value1"], 95 | ["key2", 123], 96 | // ... more key-value pairs 97 | ]; 98 | 99 | // Encryption 100 | const encryptor = new DataDomeEncryptor(hash, cid); 101 | signals.forEach(([key, value]) => encryptor.add(key, value)); 102 | const encrypted = encryptor.encrypt(); 103 | console.log('Encrypted:', encrypted); 104 | 105 | // Decryption 106 | const decryptor = new DataDomeDecryptor(hash, cid); 107 | const decrypted = decryptor.decrypt(encrypted); 108 | console.log('Decrypted:', decrypted); 109 | ``` 110 | 111 | Replace `YOUR_CLIENT_ID` and `YOUR_HASH_STRING` with your actual values. The `signals` array should contain your key-value pairs to encrypt. 112 | 113 | check out [tests/validity_check.js](./tests/validity_check.js) for a better understanding. 114 | 115 | ## Project Overview 116 | 117 | ![encryption](./assets/encryption.jpg) 118 | 119 | DataDome is a cybersecurity company valued at $33M specializing in bot detection and mitigation. This project decomposes their proprietary encryption mechanism, transforming the heavily obfuscated original code into a well-structured, documented implementation with a working decryption counterpart. 120 | 121 | The project consists of: 122 | 123 | 1. **Original Code Analysis** (`encryption_original.js`) - The obfuscated implementation extracted from DataDome 124 | 2. **Clean Implementation** (`encryption_rewrite.js`) - A structured, commented rewrite that maintains exact functionality 125 | 3. **Decryption Module** (`decryption.js`) - A complete implementation that can decrypt DataDome payloads 126 | 4. **Technical Analysis** (`technical_analysis.md`) - Deep-dive into the algorithm's cryptographic properties 127 | 128 | ## Reverse Engineering Process 129 | 130 | The reverse engineering process followed these steps (see [ENCRYPTION.md](ENCRYPTION.md) for full details): 131 | 132 | ### Step 1: Original Code Extraction 133 | 134 | The first step involved isolating the encryption routine from DataDome's client-side JavaScript: 135 | 136 | ```javascript 137 | // Original obfuscated implementation (excerpt from encryption_original.js) 138 | var Ea = function () { 139 | if (Ta && s[150][460] != s[467][62]) return ya; 140 | Math.ceil(4.1), Math.ceil(0.25), Ta = 1; 141 | var e = 1789537805, 142 | t = Math.floor(1.29), 143 | a = parseInt(1567.97), 144 | M = 9959949970, 145 | g = !0; 146 | // ... many more obfuscated lines 147 | } 148 | ``` 149 | 150 | ### Step 2: Core Function Identification 151 | 152 | We identified three key functions in the encryption process: 153 | 154 | 1. **Hash Function** (`d` in the original) - A djb2 variant for input hashing 155 | 2. **Mixing Function** (`D` in the original) - A non-linear bit mixing algorithm 156 | 3. **PRNG Generator** (`I` in the original) - Creates stateful pseudo-random generators 157 | 158 | ### Step 3: Algorithmic Analysis 159 | 160 | Through careful tracing and testing, we determined that the encryption process follows these steps: 161 | 162 | 1. Initialize PRNG with hash-derived seed and salt 163 | 2. Create a buffer for storing encrypted data 164 | 3. For each key-value pair: 165 | - Add a start marker (XORed '{' or ',') 166 | - Stringify and XOR-encrypt the key 167 | - Add a separator (XORed ':') 168 | - Stringify and XOR-encrypt the value 169 | 4. Apply a second XOR pass using a PRNG seeded with the client ID 170 | 5. Encode the result using a custom base64-like scheme 171 | 172 | ### Step 4: Rewriting as a Clean Implementation 173 | 174 | The rewrite process involved: 175 | 176 | 1. Creating a proper class structure (`DataDomeEncryptor`) 177 | 2. Renaming functions and variables for clarity 178 | 3. Adding detailed comments explaining each step 179 | 4. Removing unnecessary obfuscation 180 | 5. Preserving exact functional equivalence 181 | 182 | For a detailed walkthrough of the challenges encountered during this process, see [Challenges and Methodologies in Reverse Engineering DataDome](ENCRYPTION.md#challenges-and-methodologies-in-reverse-engineering-datadome). 183 | 184 | ## Decryption Implementation 185 | 186 | Implementing the decryption solution involved several unique challenges (see [DECRYPTION.md](DECRYPTION.md) for full details): 187 | 188 | 1. **PRNG Sequence Replication** - Exact reproduction of the encryption PRNG 189 | 2. **Custom Base64 Decoding** - Reversing the non-standard encoding scheme 190 | 3. **Dual-XOR Reversal** - Applying XOR operations in the correct sequence 191 | 4. **JSON-like Structure Parsing** - Custom parser for the recovered buffer 192 | 193 | For a detailed explanation of the decryption implementation challenges, see [Practical Decryption Challenges and Solutions](DECRYPTION.md#practical-decryption-challenges-and-solutions). 194 | 195 | ## Usage Examples 196 | 197 | ### Encryption 198 | 199 | ```javascript 200 | const { DataDomeEncryptor } = require('./encryption_rewrite.js'); 201 | 202 | // Initialize with hash and client ID 203 | const encryptor = new DataDomeEncryptor( 204 | "14D062F60A4BDE8CE8647DFC720349", 205 | "client_identifier_here" 206 | ); 207 | 208 | // Add signals (key-value pairs) 209 | encryptor.add("screenWidth", 1920); 210 | encryptor.add("userAgent", "Mozilla/5.0..."); 211 | 212 | // Generate encrypted payload 213 | const encrypted = encryptor.encrypt(); 214 | ``` 215 | 216 | ### Different Challenge Types 217 | 218 | DataDome uses two primary challenge types with slightly different encryption parameters: 219 | 220 | ```javascript 221 | // CAPTCHA challenge (default) 222 | const captchaEncryptor = new DataDomeEncryptor( 223 | "14D062F60A4BDE8CE8647DFC720349", 224 | "client_identifier_here", 225 | null, // optional salt 226 | "captcha" // challenge type 227 | ); 228 | 229 | // Interstitial challenge 230 | const interstitialEncryptor = new DataDomeEncryptor( 231 | "14D062F60A4BDE8CE8647DFC720349", // can be the same hash 232 | "client_identifier_here", 233 | null, // optional salt 234 | "interstitial" // challenge type 235 | ); 236 | 237 | // You can also switch challenge types dynamically: 238 | encryptor.setChallengeType("interstitial"); 239 | ``` 240 | 241 | #### Technical differences between challenge types: 242 | 243 | 1. **Interstitial challenges**: 244 | - Use a different hash XOR constant (-883841716) 245 | - Use a fixed HSV value ("9E9FC74889F6") 246 | - Do not apply padding handling in base64 decoding 247 | - Typically used for DataDome's interstitial pages 248 | 249 | 2. **CAPTCHA challenges (default)**: 250 | - Use the standard hash XOR constant (-1748112727) 251 | - Generate a dynamic HSV value 252 | - Apply traditional padding handling in base64 encoding/decoding 253 | - Used for the standard CAPTCHA challenge responses 254 | 255 | When decrypting data, make sure to specify the same challenge type that was used for encryption: 256 | 257 | ```javascript 258 | // Decrypting an interstitial challenge 259 | const decryptor = new DataDomeDecryptor( 260 | "14D062F60A4BDE8CE8647DFC720349", 261 | "client_identifier_here", 262 | null, // optional salt 263 | "interstitial" // must match the challenge type used for encryption 264 | ); 265 | ``` 266 | 267 | The library automatically handles the differences in encryption/decryption algorithms between challenge types. 268 | 269 | ### Decryption 270 | 271 | ```javascript 272 | const { DataDomeDecryptor } = require('./decryption.js'); 273 | 274 | // Initialize with same parameters 275 | const decryptor = new DataDomeDecryptor( 276 | "14D062F60A4BDE8CE8647DFC720349", 277 | "client_identifier_here", 278 | null, // optional salt 279 | "captcha" // challenge type - must match the encryption 280 | ); 281 | 282 | // Decrypt payload 283 | const decrypted = decryptor.decrypt(encryptedString); 284 | console.log(decrypted); 285 | // [["screenWidth", 1920], ["userAgent", "Mozilla/5.0..."]] 286 | ``` 287 | 288 | ## Technical Deep Dive 289 | 290 | For a comprehensive analysis of the encryption algorithm, including: 291 | - Constants analysis and their cryptographic significance 292 | - PRNG implementation details and statistical properties 293 | - Buffer construction and transformation processes 294 | - Custom encoding algorithm 295 | - Security and performance assessment 296 | 297 | See the [Technical Analysis](technical_analysis.md) document. 298 | 299 | ## Project Files 300 | 301 | - `encryption_original.js` - The extracted, obfuscated original code 302 | - `encryption_rewrite.js` - Clean, commented implementation 303 | - `decryption.js` - Complete decryption implementation 304 | - `ENCRYPTION.md` - Detailed explanation of the encryption process 305 | - `DECRYPTION.md` - Detailed explanation of the decryption implementation 306 | - `technical_analysis.md` - In-depth cryptographic and security analysis 307 | - `LEARN.md` - Educational guide on JavaScript reverse engineering 308 | - `example.js` - Working example demonstrating encryption and decryption 309 | - Various test files for verification and debugging 310 | 311 | ## Learn from this Project 312 | 313 | If you're interested in learning more about reverse engineering techniques used in this project, check out the [LEARN.md](LEARN.md) guide. It provides: 314 | 315 | - Detailed explanations of JavaScript obfuscation techniques 316 | - Tools and methodologies for effective reverse engineering 317 | - Step-by-step examples using DataDome's code 318 | - Common challenges and how to overcome them 319 | - Ethical considerations and resources for further learning 320 | 321 | ## Conclusion 322 | 323 | This project demonstrates how even sophisticated obfuscation techniques can be methodically reversed through careful analysis. The clean implementation provides valuable insight into client-side security techniques and serves as an educational resource for understanding advanced JavaScript obfuscation patterns. 324 | 325 | For legitimate security needs, standard cryptographic algorithms and WebCrypto APIs provide stronger protection than custom obfuscation techniques. 326 | 327 | ### Critical Evaluation of DataDome's Encryption 328 | 329 | Despite DataDome being a $33M cybersecurity company specializing in bot protection, their encryption system reveals several concerning issues: 330 | 331 | 1. **Unchanged Implementation**: The encryption algorithm has remained virtually unchanged since the first release of their captcha challenge, making it a static target for reverse engineering efforts. 332 | 333 | 2. **Security Through Obscurity**: The system relies primarily on code obfuscation rather than cryptographically secure algorithms, creating a false sense of security. 334 | 335 | 3. **Lack of Modern Cryptography**: Instead of leveraging the Web Cryptography API or established encryption standards, DataDome uses a custom algorithm with questionable security properties. 336 | 337 | 4. **Predictable Patterns**: Once the algorithm is understood, the same techniques can be applied to decrypt all of DataDome's protected communications, as demonstrated by this project. 338 | 339 | 5. **Insufficient Iteration**: Serious security systems typically evolve over time to address vulnerabilities and strengthen protection. The static nature of this implementation suggests inadequate security review processes. 340 | 341 | A more robust approach would involve: 342 | - Using established cryptographic primitives (e.g., AES-GCM) 343 | - Implementing proper key management with frequent rotation 344 | - Adopting server-side verification of encryption integrity 345 | - Regular security audits and cryptographic improvements 346 | 347 | This reverse engineering project serves as a case study in why organizations should avoid proprietary cryptographic implementations and instead rely on peer-reviewed, standardized algorithms when handling sensitive data or security-critical functions. 348 | 349 | ## Author 350 | 351 | If you found this project helpful or interesting, consider starring the repo and following me for more security research and tools, or buy me a coffee to keep me up 352 | 353 |

354 | GitHub 355 | Twitter 356 | Medium 357 | Discord 358 | Email 359 | Buy Me a Coffee 360 |

361 | 362 | --- 363 | 364 | ## Contributing 365 | 366 | Contributions are welcome! Please open issues or pull requests on [GitHub](https://github.com/glizzykingdreko/datadome-encryption). For guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md) if present. -------------------------------------------------------------------------------- /src/decryption.js: -------------------------------------------------------------------------------- 1 | /** 2 | * DataDome Decryption Implementation 3 | * 4 | * This file contains a decryption implementation for DataDome's custom encryption format. 5 | * It reverses the steps performed in encryption_rewrite.js: 6 | * 1. Decodes the custom base64-like string 7 | * 2. Reverses the cidPrng XOR step 8 | * 3. Parses the buffer entries (key-value pairs) 9 | * 4. Returns the parsed data 10 | */ 11 | 12 | // Create a helper class that exactly replicates the DataDomeEncryptor's PRNG functionality 13 | class PRNGHelper { 14 | /** 15 | * Bitwise mixing function for PRNG state. 16 | * @param {number} value 17 | * @returns {number} 18 | */ 19 | _mixInt(value) { 20 | // IMPORTANT: This needs to match exactly the implementation in DataDomeEncryptor 21 | // The exact order of operations matters for the sequence to match 22 | value ^= value << 13; 23 | value ^= value >> 17; 24 | value ^= value << 5; 25 | return value; 26 | } 27 | 28 | /** 29 | * Creates a PRNG function with internal state, used for obfuscation. 30 | * @param {number} seed 31 | * @param {number} salt 32 | * @param {boolean} useAlt 33 | * @returns {Array} 34 | */ 35 | _createPrng(seed, salt, useAlt = true) { 36 | let state = seed, round = -1, saltState = salt; 37 | // Important: In the original implementation, this._useAlt is captured as a local variable 38 | // and then reset to false immediately (see encryption_rewrite.js line 136) 39 | let useAltCopy = useAlt; 40 | let cache = null; 41 | 42 | return [function (flag) { 43 | let result; 44 | if (cache !== null) { 45 | result = cache; 46 | cache = null; 47 | } else { 48 | if (++round > 2) { 49 | state = PRNGHelper.prototype._mixInt(state); 50 | round = 0; 51 | } 52 | result = state >> (16 - 8 * round); 53 | if (useAltCopy) { 54 | result ^= --saltState; 55 | } 56 | result &= 255; 57 | if (flag) { 58 | cache = result; 59 | } 60 | } 61 | return result; 62 | }]; 63 | } 64 | } 65 | 66 | // Helper function to create a PRNG 67 | function createPrng(seed, salt, useAlt = true) { 68 | return new PRNGHelper()._createPrng(seed, salt, useAlt); 69 | } 70 | 71 | /** 72 | * Decode UTF-8 bytes to a string 73 | * @param {Array} bytes - UTF-8 encoded bytes 74 | * @returns {string} - Decoded string 75 | */ 76 | function utf8Decode(bytes) { 77 | let str = ''; 78 | for (let i = 0; i < bytes.length;) { 79 | let b = bytes[i++]; 80 | if (b < 128) { 81 | str += String.fromCharCode(b); 82 | } else if (b >= 192 && b < 224) { 83 | let b2 = bytes[i++]; 84 | str += String.fromCharCode(((b & 31) << 6) | (b2 & 63)); 85 | } else if (b >= 224 && b < 240) { 86 | let b2 = bytes[i++], b3 = bytes[i++]; 87 | str += String.fromCharCode(((b & 15) << 12) | ((b2 & 63) << 6) | (b3 & 63)); 88 | } else if (b >= 240) { 89 | let b2 = bytes[i++], b3 = bytes[i++], b4 = bytes[i++]; 90 | let codepoint = ((b & 7) << 18) | ((b2 & 63) << 12) | ((b3 & 63) << 6) | (b4 & 63); 91 | codepoint -= 0x10000; 92 | str += String.fromCharCode(0xD800 + (codepoint >> 10)); 93 | str += String.fromCharCode(0xDC00 + (codepoint & 0x3FF)); 94 | } 95 | } 96 | return str; 97 | } 98 | 99 | /** 100 | * Custom hash function used by DataDome 101 | * @param {string} str - Input string to hash 102 | * @returns {number} - Hash value 103 | */ 104 | function customHash(str) { 105 | if (!str) return 1789537805; 106 | let hash = 0; 107 | for (let i = 0; i < str.length; i++) { 108 | hash = (hash << 5) - hash + str.charCodeAt(i) | 0; 109 | } 110 | return hash !== 0 ? hash : 1789537805; 111 | } 112 | 113 | /** 114 | * DataDome Decryptor class 115 | */ 116 | class DataDomeDecryptor { 117 | /** 118 | * Create a new DataDomeDecryptor instance 119 | * @param {string} hash - Hash value used for encryption 120 | * @param {string} cid - Client ID used for encryption 121 | * @param {number} salt - Salt value used for encryption 122 | * @param {string} challengeType - Type of challenge: 'captcha' or 'interstitial' (default: 'captcha') 123 | */ 124 | constructor(hash, cid, salt, challengeType = 'captcha') { 125 | this.hash = hash; 126 | this.cid = cid; 127 | this.salt = salt || 0; 128 | this.challengeType = challengeType.toLowerCase(); 129 | 130 | // Set constants based on challenge type 131 | this._mainPrngConstant = 9959949970; 132 | this._hashXorConstant = -1748112727; 133 | this._cidPrngConstant = 1809053797; 134 | if (this.challengeType === 'interstitial') { 135 | this._hashXorConstant = -883841716; 136 | } 137 | 138 | // Calculate seeds for PRNGs 139 | this.prngSeed = this._mainPrngConstant ^ customHash(hash) ^ this._hashXorConstant; 140 | this.cidPrngSeed = this._cidPrngConstant ^ customHash(cid); 141 | 142 | // Create PRNGHelper for PRNG creation 143 | this.prngHelper = new PRNGHelper(); 144 | } 145 | 146 | /** 147 | * Decode a 6-bit encoded character according to DataDome's custom encoding 148 | * @param {number} charCode - Character code to decode 149 | * @returns {number} - Decoded 6-bit value 150 | * @private 151 | */ 152 | _decode6Bits(charCode) { 153 | if (charCode >= 97 && charCode <= 122) return charCode - 59; // 'a'..'z' → 38..63 154 | if (charCode >= 65 && charCode <= 90) return charCode - 53; // 'A'..'Z' → 12..37 155 | if (charCode >= 48 && charCode <= 57) return charCode - 46; // '0'..'9' → 2..11 156 | if (charCode === 45) return 0; // '-' → 0 157 | if (charCode === 95) return 1; // '_' → 1 158 | return 0; // fallback 159 | } 160 | 161 | /** 162 | * Decode the custom base64-like string 163 | * @param {string} encoded - Encoded string 164 | * @returns {Array} - Array of decoded bytes 165 | * @private 166 | */ 167 | _decodeCustomBase64(encoded) { 168 | let bytes = []; 169 | let n = this.salt; 170 | let i = 0; 171 | 172 | // Process full groups of 4 characters (3 bytes each) 173 | while (i + 4 <= encoded.length) { 174 | let c1 = this._decode6Bits(encoded.charCodeAt(i)); 175 | let c2 = this._decode6Bits(encoded.charCodeAt(i + 1)); 176 | let c3 = this._decode6Bits(encoded.charCodeAt(i + 2)); 177 | let c4 = this._decode6Bits(encoded.charCodeAt(i + 3)); 178 | 179 | let chunk = (c1 << 18) | (c2 << 12) | (c3 << 6) | c4; 180 | 181 | bytes.push(((chunk >> 16) & 255) ^ (--n & 255)); 182 | bytes.push(((chunk >> 8) & 255) ^ (--n & 255)); 183 | bytes.push((chunk & 255) ^ (--n & 255)); 184 | i += 4; 185 | } 186 | 187 | // Handle remaining characters (padding case) 188 | let remaining = encoded.length - i; 189 | if (remaining === 2) { 190 | // 2 chars encode 1 byte 191 | let c1 = this._decode6Bits(encoded.charCodeAt(i)); 192 | let c2 = this._decode6Bits(encoded.charCodeAt(i + 1)); 193 | let chunk = (c1 << 18) | (c2 << 12); 194 | bytes.push(((chunk >> 16) & 255) ^ (--n & 255)); 195 | } else if (remaining === 3) { 196 | // 3 chars encode 2 bytes 197 | let c1 = this._decode6Bits(encoded.charCodeAt(i)); 198 | let c2 = this._decode6Bits(encoded.charCodeAt(i + 1)); 199 | let c3 = this._decode6Bits(encoded.charCodeAt(i + 2)); 200 | let chunk = (c1 << 18) | (c2 << 12) | (c3 << 6); 201 | bytes.push(((chunk >> 16) & 255) ^ (--n & 255)); 202 | bytes.push(((chunk >> 8) & 255) ^ (--n & 255)); 203 | } 204 | 205 | return bytes; 206 | } 207 | 208 | /** 209 | * Decrypt the encoded data 210 | * @param {string} encoded - Base64-like encoded data 211 | * @returns {Array} - Array of key-value pairs 212 | */ 213 | decrypt(encoded) { 214 | // Step 1: Decode the custom base64-like string to get the XORed buffer 215 | const bufferCidPrng = this._decodeCustomBase64(encoded); 216 | 217 | // Step 2: Reverse the cidPrng XOR to get the original buffer 218 | const cidPrng = this.prngHelper._createPrng(this.cidPrngSeed, this.salt, false)[0]; 219 | const bufferWithMarker = bufferCidPrng.map(b => b ^ cidPrng()); 220 | 221 | // Step 3: Parse the buffer to extract key-value pairs 222 | return this._parseBuffer(bufferWithMarker); 223 | } 224 | 225 | /** 226 | * Parse the buffer to extract key-value pairs 227 | * @param {Array} bufferWithMarker - Buffer to parse (with marker byte) 228 | * @returns {Array} - Array of key-value pairs 229 | * @private 230 | */ 231 | _parseBuffer(bufferWithMarker) { 232 | // The last byte is a marker, remove it 233 | const buffer = bufferWithMarker.slice(0, -1); 234 | 235 | // Create PRNG for buffer parsing 236 | // This must match exactly how the encryption process creates its PRNG 237 | const prng = this.prngHelper._createPrng(this.prngSeed, this.salt, true)[0]; 238 | 239 | // Decrypt the entire buffer to get the raw JSON structure 240 | const decodedBytes = []; 241 | for (let i = 0; i < buffer.length; i++) { 242 | const b = buffer[i] ^ prng(); 243 | decodedBytes.push(b); 244 | } 245 | 246 | // Decode UTF-8 bytes properly (encryption encodes strings as UTF-8) 247 | const jsonStr = utf8Decode(decodedBytes); 248 | 249 | // Now parse this string to extract entries 250 | return this._parseJsonString(jsonStr); 251 | } 252 | 253 | /** 254 | * Parse the decrypted JSON string into key-value pairs 255 | * @param {string} jsonStr - Decrypted JSON string 256 | * @returns {Array} - Array of key-value pairs 257 | * @private 258 | */ 259 | _parseJsonString(jsonStr) { 260 | const result = []; 261 | let i = 0; 262 | 263 | // Process each character 264 | while (i < jsonStr.length) { 265 | try { 266 | // Find the start of an entry ('{' or ',') 267 | if (jsonStr[i] === '{' || jsonStr[i] === ',') { 268 | i++; // Skip the start marker 269 | 270 | // Skip whitespace 271 | while (i < jsonStr.length && /\s/.test(jsonStr[i])) i++; 272 | 273 | // Look for key (which should be a JSON string) 274 | if (jsonStr[i] !== '"') { 275 | i++; // Skip non-quote character 276 | continue; 277 | } 278 | 279 | i++; // Skip the opening quote 280 | const keyStart = i; 281 | 282 | // Read the key content 283 | while (i < jsonStr.length && jsonStr[i] !== '"') { 284 | // Handle escaped characters 285 | if (jsonStr[i] === '\\') { 286 | i += 2; // Skip escape sequence 287 | } else { 288 | i++; 289 | } 290 | } 291 | 292 | if (i >= jsonStr.length) break; 293 | 294 | const key = jsonStr.substring(keyStart, i); 295 | i++; // Skip the closing quote 296 | 297 | // Look for the separator (':') 298 | while (i < jsonStr.length && jsonStr[i] !== ':') i++; 299 | if (i >= jsonStr.length) break; 300 | i++; // Skip the separator 301 | 302 | // Skip whitespace 303 | while (i < jsonStr.length && /\s/.test(jsonStr[i])) i++; 304 | if (i >= jsonStr.length) break; 305 | 306 | // Process the value based on its type 307 | let value; 308 | const valueStart = i; 309 | 310 | if (jsonStr[i] === '"') { 311 | // String value 312 | i++; // Skip opening quote 313 | let valueContent = ''; 314 | let escaped = false; 315 | 316 | while (i < jsonStr.length) { 317 | if (escaped) { 318 | valueContent += jsonStr[i]; 319 | escaped = false; 320 | } else if (jsonStr[i] === '\\') { 321 | valueContent += jsonStr[i]; 322 | escaped = true; 323 | } else if (jsonStr[i] === '"') { 324 | break; 325 | } else { 326 | valueContent += jsonStr[i]; 327 | } 328 | i++; 329 | } 330 | 331 | if (i < jsonStr.length) i++; // Skip closing quote 332 | 333 | // Try to properly parse the string with JSON.parse to handle escapes 334 | try { 335 | value = JSON.parse(`"${valueContent.replace(/"/g, '\\"')}"`); 336 | } catch (e) { 337 | // If parsing fails, use the raw string 338 | value = this._unescapeString(valueContent); 339 | } 340 | } else if (jsonStr[i] === '{') { 341 | // Object value - more complex handling needed, but for now just extract as string 342 | let nestLevel = 1; 343 | i++; // Skip opening brace 344 | let objectStr = '{'; 345 | 346 | while (i < jsonStr.length && nestLevel > 0) { 347 | if (jsonStr[i] === '{') { 348 | nestLevel++; 349 | } else if (jsonStr[i] === '}') { 350 | nestLevel--; 351 | } else if (jsonStr[i] === '"') { 352 | // Skip the entire string including any braces inside it 353 | objectStr += jsonStr[i++]; 354 | while (i < jsonStr.length && jsonStr[i] !== '"') { 355 | if (jsonStr[i] === '\\') { 356 | objectStr += jsonStr[i++]; 357 | if (i < jsonStr.length) objectStr += jsonStr[i++]; 358 | } else { 359 | objectStr += jsonStr[i++]; 360 | } 361 | } 362 | if (i < jsonStr.length) objectStr += jsonStr[i]; // Add closing quote 363 | } 364 | 365 | if (i < jsonStr.length) { 366 | objectStr += jsonStr[i]; 367 | i++; 368 | } 369 | } 370 | 371 | try { 372 | value = JSON.parse(objectStr); 373 | } catch (e) { 374 | value = objectStr; 375 | } 376 | } else if (jsonStr[i] === '[') { 377 | // Array value - similar to object handling 378 | let nestLevel = 1; 379 | i++; // Skip opening bracket 380 | let arrayStr = '['; 381 | 382 | while (i < jsonStr.length && nestLevel > 0) { 383 | if (jsonStr[i] === '[') { 384 | nestLevel++; 385 | } else if (jsonStr[i] === ']') { 386 | nestLevel--; 387 | } else if (jsonStr[i] === '"') { 388 | // Skip the entire string including any brackets inside it 389 | arrayStr += jsonStr[i++]; 390 | while (i < jsonStr.length && jsonStr[i] !== '"') { 391 | if (jsonStr[i] === '\\') { 392 | arrayStr += jsonStr[i++]; 393 | if (i < jsonStr.length) arrayStr += jsonStr[i++]; 394 | } else { 395 | arrayStr += jsonStr[i++]; 396 | } 397 | } 398 | if (i < jsonStr.length) arrayStr += jsonStr[i]; // Add closing quote 399 | } 400 | 401 | if (i < jsonStr.length) { 402 | arrayStr += jsonStr[i]; 403 | i++; 404 | } 405 | } 406 | 407 | try { 408 | value = JSON.parse(arrayStr); 409 | } catch (e) { 410 | value = arrayStr; 411 | } 412 | } else if (/[0-9-]/.test(jsonStr[i])) { 413 | // Number value 414 | let numStr = ''; 415 | while (i < jsonStr.length && /[0-9.eE+-]/.test(jsonStr[i])) { 416 | numStr += jsonStr[i++]; 417 | } 418 | value = parseFloat(numStr); 419 | } else if (jsonStr.substring(i, i + 4) === 'true') { 420 | value = true; 421 | i += 4; 422 | } else if (jsonStr.substring(i, i + 5) === 'false') { 423 | value = false; 424 | i += 5; 425 | } else if (jsonStr.substring(i, i + 4) === 'null') { 426 | value = null; 427 | i += 4; 428 | } else { 429 | // Unknown value type - just skip this character 430 | i++; 431 | continue; 432 | } 433 | // Add entry to result 434 | result.push([this._unescapeString(key), value]); 435 | } else { 436 | // Skip any other characters 437 | i++; 438 | } 439 | } catch (error) { 440 | // If any parsing error occurs, skip to the next character 441 | console.log("Error parsing JSON", error); 442 | i++; 443 | } 444 | } 445 | 446 | return result; 447 | } 448 | 449 | /** 450 | * Unescape a string similarly to how JavaScript would with JSON.parse 451 | * but without throwing errors on invalid escape sequences 452 | * @param {string} str - String to unescape 453 | * @returns {string} - Unescaped string 454 | * @private 455 | */ 456 | _unescapeString(str) { 457 | if (typeof str !== 'string') return str; 458 | 459 | return str.replace(/\\(.)/g, function(match, char) { 460 | switch (char) { 461 | case 'n': return '\n'; 462 | case 'r': return '\r'; 463 | case 't': return '\t'; 464 | case 'b': return '\b'; 465 | case 'f': return '\f'; 466 | case '\\': return '\\'; 467 | case '"': return '"'; 468 | default: return char; // For \u sequences, just keep as is 469 | } 470 | }); 471 | } 472 | 473 | /** 474 | * Gets the challenge type currently being used 475 | * @returns {string} 476 | */ 477 | getChallengeType() { 478 | return this.challengeType; 479 | } 480 | 481 | /** 482 | * Updates the challenge type and recalculates PRNG seeds 483 | * @param {string} challengeType - 'captcha' or 'interstitial' 484 | */ 485 | setChallengeType(challengeType) { 486 | this.challengeType = challengeType.toLowerCase(); 487 | 488 | // Update constants based on challenge type 489 | if (this.challengeType === 'interstitial') { 490 | this._mainPrngConstant = 9959949970; 491 | this._hashXorConstant = -883841716; 492 | this._cidPrngConstant = 1809053797; 493 | } else { 494 | this._mainPrngConstant = 9959949970; 495 | this._hashXorConstant = -1748112727; 496 | this._cidPrngConstant = 1809053797; 497 | } 498 | 499 | // Recalculate seeds 500 | this.prngSeed = this._mainPrngConstant ^ customHash(this.hash) ^ this._hashXorConstant; 501 | this.cidPrngSeed = this._cidPrngConstant ^ customHash(this.cid); 502 | } 503 | } 504 | 505 | module.exports = { DataDomeDecryptor }; -------------------------------------------------------------------------------- /docs/LEARN.md: -------------------------------------------------------------------------------- 1 | # Learning Reverse Engineering through DataDome's Encryption 2 | 3 | This document provides an educational guide on JavaScript reverse engineering, using the DataDome encryption system as a practical case study. Whether you're a security researcher, a developer interested in understanding obfuscation techniques, or someone looking to learn about cryptographic implementations, this guide offers insights into the methodical process of understanding and reimplementing complex obfuscated code. 4 | 5 | 6 |
7 | Status: Complete 8 | Type: Research 9 | License: MIT 10 | npm version 11 | GitHub stars 12 | GitHub repo 13 |
14 | 15 |
16 | 17 |
18 | Check the Python version 19 | Read the full article on Medium 20 |
21 |
22 | 23 | 24 | 25 | ## Navigation 26 | 27 | | Document | Description | 28 | |----------|-------------| 29 | | [README.md](../README.md) | Project overview and introduction | 30 | | [ENCRYPTION.md](./ENCRYPTION.md) | Detailed analysis of the encryption algorithm | 31 | | [DECRYPTION.md](./DECRYPTION.md) | Implementation of the decryption process | 32 | | [technical_analysis.md](./technical_analysis.md) | Technical deep-dive into cryptographic properties | 33 | | [LEARN.md](./LEARN.md) | Educational guide on reverse engineering techniques | 34 | 35 | ## Table of Contents 36 | 37 | 1. [Understanding JavaScript Obfuscation](#understanding-javascript-obfuscation) 38 | 2. [Tools of the Trade](#tools-of-the-trade) 39 | 3. [Reverse Engineering Methodology](#reverse-engineering-methodology) 40 | 4. [Case Study: DataDome's Encryption](#case-study-datadomes-encryption) 41 | 5. [Common Challenges and Solutions](#common-challenges-and-solutions) 42 | 6. [Ethical Considerations](#ethical-considerations) 43 | 7. [Learning Resources](#learning-resources) 44 | 45 | --- 46 | **Need DataDome Bypass Solutions?** 47 | 48 | If you need a reliable DataDome bypass solution for your project, turn to the experts who truly understand the technology. My company, TakionAPI, offers professional anti-bot bypass APIs with proven effectiveness against DataDome and other bot-defense systems. 49 | 50 | No more worrying about understanding, reversing, and solving the challenge yourself, or about keeping it up to date every day. One simple API call does it all. 51 | 52 | We provide free trials, example implementations, and setup assistance to make the entire process easy and smooth. 53 | - 📄 [Check our straightforward documentation](https://docs.takionapi.tech) 54 | - 🚀 [Start your trial](https://dashboard.takionapi.tech) 55 | - 💬 [Contact us on Discord](https://takionapi.tech/discord) for custom development and support. 56 | 57 | **Visit [TakionAPI.tech](https://takionapi.tech) for real, high-quality anti-bot bypass solutions — we know what we're doing.** 58 | 59 | --- 60 | 61 | ## Understanding JavaScript Obfuscation 62 | 63 | JavaScript obfuscation refers to techniques that make code difficult to understand while preserving its functionality. Here are common obfuscation techniques identified in the DataDome code: 64 | 65 | ### 1. Meaningless Variable and Function Names 66 | 67 | Original obfuscated code uses non-descriptive identifiers: 68 | 69 | ```javascript 70 | var Ta = 1, ya; 71 | var Ea = function () { 72 | if (Ta && s[150][460] != s[467][62]) return ya; 73 | Math.ceil(4.1), Math.ceil(0.25), Ta = 1; 74 | var e = 1789537805, 75 | t = Math.floor(1.29), 76 | a = parseInt(1567.97), 77 | M = 9959949970, 78 | g = !0; 79 | // ... 80 | } 81 | ``` 82 | 83 | ### 2. Dead Code Injection 84 | 85 | Obfuscators add code that never executes or has no effect: 86 | 87 | ```javascript 88 | // Meaningless operations that don't affect results 89 | Math.ceil(4.1), Math.ceil(0.25); 90 | 91 | // Complex conditions that always evaluate to the same result 92 | if (2 * (Le | a) + 3 * ~(Le | a) - 2 * (~Le | a) - ~(Le & a) > 93 | -1 * (ra & ~s) + 2 * ~(ra & s) + 1 * ~(ra ^ s) - 3 * ~(ra | s) - 2 * ~(ra | ~s)) { 94 | // This condition is designed to always evaluate to true 95 | } 96 | ``` 97 | 98 | ### 3. Control Flow Obfuscation 99 | 100 | Code is structured to make the execution flow difficult to follow: 101 | 102 | ```javascript 103 | // Function aliasing and indirect references 104 | var d = function(r) { /* hash function */ }; 105 | var D = function(e) { /* mixing function */ }; 106 | 107 | // Later used through multiple levels of indirection 108 | var result = D(d(input)); 109 | ``` 110 | 111 | ### 4. String Concealment 112 | 113 | Strings are hidden through various techniques: 114 | 115 | ```javascript 116 | // String construction through character codes 117 | var hidden = String.fromCharCode(120, 116, 49); // "xt1" 118 | 119 | // Split strings across variables 120 | var p1 = "co"; 121 | var p2 = "nc"; 122 | var p3 = "at"; 123 | var method = p1 + p2 + p3; // "concat" 124 | ``` 125 | 126 | ### 5. Dynamic Evaluation 127 | 128 | Using `eval()` or dynamic property access to hide functionality: 129 | 130 | ```javascript 131 | // Property access obfuscation 132 | var obj = {}; 133 | obj["co"+"ncat"] = function(a, b) { return a + b; }; 134 | var result = obj["co"+"ncat"]("hello", "world"); 135 | ``` 136 | 137 | ## Tools of the Trade 138 | 139 | Successful reverse engineering requires the right tools. Here are the essential tools used in our DataDome case study: 140 | 141 | ### 1. Browser Developer Tools 142 | 143 | Chrome/Firefox DevTools provide: 144 | - JavaScript debugging with breakpoints 145 | - Call stack visualization 146 | - Variable inspection 147 | - Network request monitoring 148 | 149 | **Usage example**: Setting breakpoints in the DataDome encryption function to observe intermediate values. 150 | 151 | ### 2. Code Beautifiers and Deobfuscators 152 | 153 | - [js-beautify](https://beautifier.io/) - Formats minified code 154 | - [de4js](https://lelinhtinh.github.io/de4js/) - Attempts to deobfuscate common patterns 155 | - [JStillery](https://github.com/mindedsecurity/JStillery) - Advanced JavaScript deobfuscation 156 | 157 | **Usage example**: Reformatting DataDome's obfuscated code to make the structure visible. 158 | 159 | ### 3. Static Analysis Tools 160 | 161 | - [JSNice](http://jsnice.org/) - Predicts variable names and types 162 | - [Babel AST Explorer](https://astexplorer.net/) - Visualize code structure 163 | 164 | **Usage example**: Analyzing the abstract syntax tree of DataDome's encryption functions. 165 | 166 | ### 4. Dynamic Analysis and Debugging 167 | 168 | - [Fiddler](https://www.telerik.com/fiddler) or [Charles Proxy](https://www.charlesproxy.com/) - Intercept and modify network traffic 169 | - [NodeJS debugger](https://nodejs.org/api/debugger.html) - For offline analysis 170 | 171 | **Usage example**: Capturing DataDome's encryption inputs and outputs to verify our understanding. 172 | 173 | ### 5. Code Visualization 174 | 175 | - [JavaScript Visualizer](https://javascriptvisualizer.com/) - Visualize execution flow 176 | - Custom buffer visualization tools (as shown in DECRYPTION.md) 177 | 178 | **Usage example**: Visualizing the PRNG state evolution during encryption. 179 | 180 | ## Reverse Engineering Methodology 181 | 182 | Our DataDome reverse engineering followed this systematic approach: 183 | 184 | > 🎓 **From Learning to Application** 185 | > 186 | > Learning reverse engineering through projects like this DataDome analysis is just the beginning. If you're looking to apply these skills but don't want to build everything from scratch, [TakionAPI.tech](https://takionapi.tech) offers professional DataDome bypass solutions developed using the same techniques described in this guide. 187 | 188 | ### 1. Reconnaissance 189 | 190 | - Identify the target functionality (encryption in this case) 191 | - Collect samples of inputs and outputs 192 | - Understand the context and purpose 193 | 194 | ### 2. Initial Analysis 195 | 196 | - Format and beautify the code 197 | - Identify entry points and main functions 198 | - Map dependencies between functions 199 | 200 | ### 3. Function Decomposition 201 | 202 | For each identified function: 203 | 1. Determine inputs and outputs 204 | 2. Test with controlled inputs 205 | 3. Document behavior and purpose 206 | 4. Rename for clarity 207 | 208 | ### 4. Static Analysis 209 | 210 | - Trace variable usage 211 | - Identify critical constants 212 | - Map control flow 213 | - Recognize patterns and algorithms 214 | 215 | ### 5. Dynamic Analysis 216 | 217 | - Set breakpoints at key points 218 | - Observe variable values during execution 219 | - Trace state changes 220 | - Validate hypotheses 221 | 222 | ### 6. Incremental Rewriting 223 | 224 | - Start with small, well-understood functions 225 | - Rewrite with meaningful names and comments 226 | - Validate each rewritten function independently 227 | - Gradually expand to full system 228 | 229 | ### 7. Validation and Testing 230 | 231 | - Test with diverse inputs 232 | - Compare outputs with original 233 | - Ensure edge cases are handled correctly 234 | - Document any discrepancies 235 | 236 | ## Case Study: DataDome's Encryption 237 | 238 | Let's apply the methodology to a specific component of DataDome's encryption: the PRNG implementation. 239 | 240 | ### Challenge Type Identification 241 | 242 | DataDome uses different challenge types with slightly different parameters: 243 | 244 | ```javascript 245 | // Determining challenge type from initialization parameters 246 | function identifyChallenge(hash, constants) { 247 | if (constants.hashXor === -883841716) { 248 | console.log("Identified as Interstitial challenge"); 249 | return "interstitial"; 250 | } else if (constants.hashXor === -1748112727) { 251 | console.log("Identified as CAPTCHA challenge"); 252 | return "captcha"; 253 | } else { 254 | console.log("Unknown challenge type"); 255 | return "unknown"; 256 | } 257 | } 258 | 259 | // Test by extracting constants from code 260 | function extractConstants(code) { 261 | // Look for XOR constants in the code 262 | const hashXorMatch = code.match(/customHash\(hash\)\s*\^\s*(-\d+)/); 263 | return { 264 | hashXor: hashXorMatch ? parseInt(hashXorMatch[1]) : null 265 | }; 266 | } 267 | ``` 268 | 269 | ### 1. Original Obfuscated PRNG 270 | 271 | ```javascript 272 | function I(e, t) { 273 | var a = e, 274 | n = -1, 275 | c = t, 276 | i = g; 277 | g = !1; 278 | var r = null; 279 | return [function (e) { 280 | var t; 281 | if (null !== r) { 282 | t = r; 283 | r = null; 284 | } else { 285 | ++n > 2 && (a = D(a), n = 0); 286 | t = a >> 16 - 8 * n; 287 | t ^= i ? --c : 0; 288 | t &= 255; 289 | e && (r = t); 290 | } 291 | return t; 292 | }]; 293 | } 294 | ``` 295 | 296 | ### 2. Analysis Process 297 | 298 | 1. **Identify inputs and outputs**: 299 | - Inputs: `e` (seed), `t` (salt), `g` (mode flag) 300 | - Output: A function that generates pseudo-random bytes 301 | 302 | 2. **Identify state variables**: 303 | - `a`: Internal state (initialized with seed) 304 | - `n`: Round counter (-1 initially) 305 | - `c`: Salt value (decremented with use) 306 | - `i`: Mode flag (copied from `g`) 307 | - `r`: Cache value (null initially) 308 | 309 | 3. **Trace execution flow**: 310 | - State mixing occurs when round counter exceeds 2 311 | - Different 8-bit segments are extracted in rotation 312 | - Salt is optionally XORed with output 313 | - Output is cached when the flag parameter is true 314 | 315 | 4. **Test with controlled inputs**: 316 | ```javascript 317 | // Test code 318 | const prng = I(0x12345678, 0xABCDEF)[0]; 319 | for (let i = 0; i < 10; i++) { 320 | console.log(prng().toString(16)); 321 | } 322 | ``` 323 | 324 | 5. **Rewrite with clarity**: 325 | ```javascript 326 | /** 327 | * Creates a PRNG function with internal state. 328 | * @param {number} seed - Initial state value 329 | * @param {number} salt - Salt for additional entropy 330 | * @param {boolean} useAlt - Whether to use salt XOR 331 | * @returns {function(boolean): number} - PRNG function 332 | */ 333 | function createPrng(seed, salt, useAlt = true) { 334 | let state = seed, round = -1, saltState = salt; 335 | let cache = null; 336 | 337 | return function(cacheFlag) { 338 | let result; 339 | 340 | // Use cached value if available 341 | if (cache !== null) { 342 | result = cache; 343 | cache = null; 344 | } else { 345 | // Mix state every 3 rounds 346 | if (++round > 2) { 347 | state = mixInt(state); 348 | round = 0; 349 | } 350 | 351 | // Extract different byte segments based on round 352 | result = state >> (16 - 8 * round); 353 | 354 | // Optionally XOR with decrementing salt 355 | if (useAlt) { 356 | result ^= --saltState; 357 | } 358 | 359 | // Ensure byte range 360 | result &= 255; 361 | 362 | // Cache result if flag is true 363 | if (cacheFlag) { 364 | cache = result; 365 | } 366 | } 367 | 368 | return result; 369 | }; 370 | } 371 | ``` 372 | 373 | 6. **Validation**: 374 | Compare outputs of original and rewritten functions with identical inputs. 375 | 376 | ## Common Challenges and Solutions 377 | 378 | ### Challenge 1: Tracking State Across Function Calls 379 | 380 | **Problem**: Functions like the PRNG maintain state across multiple calls, making it difficult to understand behavior from a single call. 381 | 382 | **Solution**: Create harness code that tracks state changes: 383 | 384 | ```javascript 385 | function trackPRNGState(seed, salt) { 386 | const prng = createPrng(seed, salt); 387 | let outputs = []; 388 | 389 | // Generate sequence and record outputs 390 | for (let i = 0; i < 20; i++) { 391 | outputs.push(prng()); 392 | } 393 | 394 | // Analyze patterns 395 | console.log("Output sequence:", outputs); 396 | console.log("Repeating patterns:", findPatterns(outputs)); 397 | 398 | return outputs; 399 | } 400 | ``` 401 | 402 | ### Challenge 2: Understanding Complex Algorithms 403 | 404 | **Problem**: Recognizing standard algorithms behind obfuscated implementations. 405 | 406 | **Solution**: Compare with known algorithm references and use controlled inputs: 407 | 408 | ```javascript 409 | // Test known algorithms with same inputs 410 | function compareWithKnownAlgorithms(input) { 411 | console.log("DataDome custom hash:", customHash(input)); 412 | console.log("DJB2 hash:", djb2Hash(input)); 413 | console.log("FNV-1a hash:", fnv1aHash(input)); 414 | 415 | // Determine if they match 416 | if (customHash(input) === djb2Hash(input)) { 417 | console.log("Matches DJB2!"); 418 | } 419 | } 420 | ``` 421 | 422 | ### Challenge 3: Dealing with Obfuscated Constants 423 | 424 | **Problem**: Important constants hidden in calculations or transformations. 425 | 426 | **Solution**: Track constant usage and derive significance: 427 | 428 | ```javascript 429 | // Analyze a suspicious constant 430 | const suspiciousConstant = 9959949970; 431 | 432 | // Check binary representation 433 | console.log("Binary:", suspiciousConstant.toString(2)); 434 | console.log("Bit count:", countBits(suspiciousConstant)); 435 | 436 | // Test mathematical properties 437 | console.log("Prime factors:", primeFactors(suspiciousConstant)); 438 | console.log("Related to known constants?", findRelatedConstants(suspiciousConstant)); 439 | ``` 440 | 441 | ### Challenge 4: Handling Mixed Data Types 442 | 443 | **Problem**: Complex data structures with various types that affect encoding. 444 | 445 | **Solution**: Create comprehensive test data covering all types: 446 | 447 | ```javascript 448 | const testSuite = [ 449 | ["string", "simple string"], 450 | ["empty", ""], 451 | ["number", 12345], 452 | ["float", 123.456], 453 | ["boolean", true], 454 | ["null", null], 455 | ["undefined", undefined], 456 | ["specialChars", "!@#$%^&*()"], 457 | ["unicode", "こんにちは世界"], 458 | ["surrogates", "𝌆"], 459 | ["array", [1,2,3]], 460 | ["object", {"a": 1, "b": 2}] 461 | ]; 462 | 463 | // Test encryption/decryption with each 464 | testSuite.forEach(([type, value]) => { 465 | console.log(`Testing ${type}:`, value); 466 | const encrypted = encrypt(type, value); 467 | const decrypted = decrypt(encrypted); 468 | console.log("Round-trip success:", compare(value, decrypted[1])); 469 | }); 470 | ``` 471 | 472 | ## Ethical Considerations 473 | 474 | Reverse engineering carries ethical and legal responsibilities: 475 | 476 | 1. **Respect Intellectual Property**: 477 | - Use reverse engineering for interoperability, research, or educational purposes 478 | - Don't use findings to create competing commercial products 479 | 480 | 2. **Responsible Disclosure**: 481 | - If security vulnerabilities are discovered, follow responsible disclosure practices 482 | - Give vendors reasonable time to address issues before public disclosure 483 | 484 | 3. **Legal Boundaries**: 485 | - Be aware of local laws regarding reverse engineering 486 | - Terms of Service may prohibit reverse engineering (though enforceability varies) 487 | 488 | 4. **Academic and Educational Use**: 489 | - Document findings for educational purposes 490 | - Share knowledge in a way that emphasizes understanding over exploitation 491 | 492 | 5. **Positive Impact**: 493 | - Focus on improving security and understanding 494 | - Use findings to promote better practices 495 | 496 | ## Learning Resources 497 | 498 | To further your reverse engineering skills: 499 | 500 | ### Books 501 | 502 | - "The Web Application Hacker's Handbook" by Dafydd Stuttard and Marcus Pinto 503 | - "JavaScript: The Definitive Guide" by David Flanagan 504 | - "Gray Hat Python" by Justin Seitz 505 | - "Practical Malware Analysis" by Michael Sikorski and Andrew Honig 506 | 507 | ### Online Resources 508 | 509 | - [Mozilla Developer Network (MDN)](https://developer.mozilla.org/) 510 | - [OWASP Web Security Testing Guide](https://owasp.org/www-project-web-security-testing-guide/) 511 | - [Computerphile's YouTube videos on encryption](https://www.youtube.com/user/Computerphile) 512 | - [Crypto-IT](https://www.crypto-it.net/) for cryptographic algorithm explanations 513 | 514 | ### Practice Platforms 515 | 516 | - [CryptoPals Challenges](https://cryptopals.com/) 517 | - [HackTheBox](https://www.hackthebox.eu/) 518 | - [CTFlearn](https://ctflearn.com/) 519 | - [Google Gruyere](https://google-gruyere.appspot.com/) 520 | 521 | ### Communities 522 | 523 | - [Open Web Application Security Project (OWASP)](https://owasp.org/) 524 | - [Reddit's /r/ReverseEngineering](https://www.reddit.com/r/ReverseEngineering/) 525 | - [Stack Overflow](https://stackoverflow.com/) 526 | - [DEF CON Groups](https://www.defcon.org/html/defcon-groups/dc-groups-index.html) 527 | 528 | ## Conclusion 529 | 530 | Reverse engineering is both an art and a science, requiring patience, analytical thinking, and creative problem-solving. The DataDome case study demonstrates how even sophisticated obfuscation can be systematically unraveled to reveal the underlying algorithms and logic. 531 | 532 | By understanding how obfuscation techniques work, you gain not only the ability to reverse engineer complex systems but also the knowledge to build more secure software. Remember that the most valuable aspect of reverse engineering is not the end result but the deep understanding gained through the process. 533 | 534 | --- 535 | 536 | ## Author 537 | 538 | If you found this project helpful or interesting, consider starring the repo and following me for more security research and tools, or buy me a coffee to keep me up. 539 | 540 | Stay in touch with me via discord or mail or anything. 541 | 542 |

543 | GitHub 544 | Twitter 545 | Medium 546 | Discord 547 | Email 548 | Buy Me a Coffee 549 |

550 | -------------------------------------------------------------------------------- /docs/ENCRYPTION.md: -------------------------------------------------------------------------------- 1 | # ENCRYPTION.md 2 | 3 | This document provides a deep-dive analysis of the encryption logic implemented in `encryption_rewrite.js`. The script is designed to obfuscate and encrypt key-value data pairs using a custom, multi-stage process involving PRNGs, custom base64-like encoding, and several layers of XOR and mixing operations. The logic is intentionally complex and mimics real-world obfuscated payload builders. 4 | 5 |
6 | Status: Complete 7 | Type: Research 8 | License: MIT 9 | npm version 10 | GitHub stars 11 | GitHub repo 12 |
13 | 14 |
15 | 16 |
17 | Check the Python version 18 | Read the full article on Medium 19 |
20 |
21 | 22 | ![encryption](../assets/encryption.jpg) 23 | 24 | ## Navigation 25 | 26 | | Document | Description | 27 | |----------|-------------| 28 | | [README.md](../README.md) | Project overview and introduction | 29 | | [ENCRYPTION.md](./ENCRYPTION.md) | Detailed analysis of the encryption algorithm | 30 | | [DECRYPTION.md](./DECRYPTION.md) | Implementation of the decryption process | 31 | | [technical_analysis.md](./technical_analysis.md) | Technical deep-dive into cryptographic properties | 32 | | [LEARN.md](./LEARN.md) | Educational guide on reverse engineering techniques | 33 | 34 | ## High-Level Encryption Pipeline 35 | 36 | 1. **Initialization**: The encryptor is initialized with a `hash`, `cid`, and optional `salt`. 37 | 2. **Buffer Construction**: Each key-value pair is encoded and obfuscated, then appended to an internal buffer. 38 | 3. **PRNG XOR**: The buffer is XORed with a PRNG sequence seeded by the `cid` and `salt`. 39 | 4. **Marker Byte**: A marker byte is appended, also obfuscated with the PRNG. 40 | 5. **Custom Base64-like Encoding**: The resulting byte array is encoded into a string using a custom 6-bit mapping and further obfuscated with the salt. 41 | 6. **Output**: The final string is the encrypted payload. 42 | 43 | **Note** What the salt is doing (in really simplified terms) is to change the last char of the encryption string. It will be so "based on timestamp" for this reason when comparing 2 encrypted strings made with the same `cid`, `hash` and signals, be sure to remove the last char before comparing them (unless you are using the same salt). Take a look into [validity_check.js](../tests/validity_check.js) 44 | 45 | --- 46 | 47 | ## Table of Contents 48 | - [ENCRYPTION.md](#encryptionmd) 49 | - [Navigation](#navigation) 50 | - [High-Level Encryption Pipeline](#high-level-encryption-pipeline) 51 | - [Table of Contents](#table-of-contents) 52 | - [Class: `DataDomeEncryptor`](#class-datadomeencryptor) 53 | - [Constructor](#constructor) 54 | - [Key Methods](#key-methods) 55 | - [`_generateHsv()`](#_generatehsv) 56 | - [`_customHash(str)`](#_customhashstr) 57 | - [`_encode6Bits(value)`](#_encode6bitsvalue) 58 | - [`_mixInt(value)`](#_mixintvalue) 59 | - [`_createPrng(seed, salt)`](#_createprngseed-salt) 60 | - [`_utf8Xor(str, prng)`](#_utf8xorstr-prng) 61 | - [`_safeJson(value)`](#_safejsonvalue) 62 | - [`_encodePayload(byteArr, salt, encode6Bits)`](#_encodepayloadbytearr-salt-encode6bits) 63 | - [`_resetEncryptionState()`](#_resetencryptionstate) 64 | - [`_addSignal(key, value)`](#_addsignalkey-value) 65 | - [`_buildPayload(cid)`](#_buildpayloadcid) 66 | - [`add(key, value)`](#addkey-value) 67 | - [`encrypt()`](#encrypt) 68 | - [`setChallengeType(challengeType)`](#setchallengetypechallengetype) 69 | - [`getChallengeType()`](#getchallengetype) 70 | - [Step-by-Step Encryption Example](#step-by-step-encryption-example) 71 | - [Debug Files](#debug-files) 72 | - [Notes on Obfuscation](#notes-on-obfuscation) 73 | - [Diagrams](#diagrams) 74 | - [Summary Table](#summary-table) 75 | - [Conclusion](#conclusion) 76 | - [Challenges and Methodologies in Reverse Engineering DataDome](#challenges-and-methodologies-in-reverse-engineering-datadome) 77 | - [Methodology and Testing Approach](#methodology-and-testing-approach) 78 | - [1. Initial Reconnaissance and Capture](#1-initial-reconnaissance-and-capture) 79 | - [2. Static Analysis Challenges](#2-static-analysis-challenges) 80 | - [3. Dynamic Analysis and Testing Methods](#3-dynamic-analysis-and-testing-methods) 81 | - [4. Specific Difficulties Encountered](#4-specific-difficulties-encountered) 82 | - [4.1 PRNG Analysis Challenges](#41-prng-analysis-challenges) 83 | - [4.2 Buffer Construction Issues](#42-buffer-construction-issues) 84 | - [4.3 Verification Challenges](#43-verification-challenges) 85 | - [Test Cases and Validation](#test-cases-and-validation) 86 | - [1. Component-Level Testing](#1-component-level-testing) 87 | - [2. Intermediate Output Verification](#2-intermediate-output-verification) 88 | - [3. Full End-to-End Verification](#3-full-end-to-end-verification) 89 | - [4. Edge Case Testing](#4-edge-case-testing) 90 | - [Learnings and Insights](#learnings-and-insights) 91 | - [DataDome Encryption: Reverse Engineering Process](#datadome-encryption-reverse-engineering-process) 92 | - [Original Code Analysis](#original-code-analysis) 93 | - [Obfuscation Techniques Identified](#obfuscation-techniques-identified) 94 | - [Main Encryption Function Extraction](#main-encryption-function-extraction) 95 | - [Core Functions Decomposition](#core-functions-decomposition) 96 | - [Function 1: Custom Hash Function (d)](#function-1-custom-hash-function-d) 97 | - [Function 2: Bitwise Mixing Function (D)](#function-2-bitwise-mixing-function-d) 98 | - [Function 3: 6-bit Encoding (N)](#function-3-6-bit-encoding-n) 99 | - [Function 4: PRNG Generator (I)](#function-4-prng-generator-i) 100 | - [Main Encryption Logic Analysis](#main-encryption-logic-analysis) 101 | - [Key Function 1: UTF-8 Conversion and XOR (x)](#key-function-1-utf-8-conversion-and-xor-x) 102 | - [Key Function 2: JSON Stringify (z)](#key-function-2-json-stringify-z) 103 | - [Key Function 3: Signal Addition (y)](#key-function-3-signal-addition-y) 104 | - [Key Function 4: Payload Building](#key-function-4-payload-building) 105 | - [Key Function 5: Custom Base64 Encoding (A)](#key-function-5-custom-base64-encoding-a) 106 | - [Structuring the Clean Implementation](#structuring-the-clean-implementation) 107 | - [Verification Process](#verification-process) 108 | - [Conclusion](#conclusion-1) 109 | - [Author](#author) 110 | 111 | --- 112 | 113 | **Need DataDome Bypass Solutions?** 114 | 115 | If you need a reliable DataDome bypass solution for your project, turn to the experts who truly understand the technology. My company, TakionAPI, offers professional anti-bot bypass APIs with proven effectiveness against DataDome and other bot-defense systems. 116 | 117 | No more worrying about understanding, reversing, and solving the challenge yourself, or about keeping it up to date every day. One simple API call does it all. 118 | 119 | We provide free trials, example implementations, and setup assistance to make the entire process easy and smooth. 120 | - 📄 [Check our straightforward documentation](https://docs.takionapi.tech) 121 | - 🚀 [Start your trial](https://dashboard.takionapi.tech) 122 | - 💬 [Contact us on Discord](https://takionapi.tech/discord) for custom development and support. 123 | 124 | **Visit [TakionAPI.tech](https://takionapi.tech) for real, high-quality anti-bot bypass solutions — we know what we're doing.** 125 | 126 | --- 127 | 128 | ## Class: `DataDomeEncryptor` 129 | 130 | ### Constructor 131 | - **Inputs**: `hash`, `cid`, `salt` (optional), `challengeType` (optional, default: 'captcha') 132 | - **Purpose**: Sets up the encryption state, including PRNG seeds and internal buffer. 133 | - **Challenge Types**: 134 | - `captcha`: Default challenge type, uses standard DataDome CAPTCHA parameters 135 | - `interstitial`: Alternative challenge type with different XOR constants and fixed HSV value 136 | 137 | ### Key Methods 138 | 139 | #### `_generateHsv()` 140 | - Generates a pseudo-random HSV string based on the hash and random values. 141 | - Used as a hidden value in the encryption process (not directly in the buffer). 142 | 143 | #### `_customHash(str)` 144 | - Custom string hashing function, returns a 32-bit integer. 145 | - Used to seed PRNGs. 146 | 147 | #### `_encode6Bits(value)` 148 | - Maps a 6-bit value (0-63) to a custom character code for the payload. 149 | - The mapping is non-standard and intentionally obfuscated. 150 | 151 | #### `_mixInt(value)` 152 | - Bitwise mixing function for PRNG state. 153 | - Used to further obfuscate PRNG state transitions. 154 | 155 | #### `_createPrng(seed, salt)` 156 | - Returns a PRNG function with internal state. 157 | - The PRNG is used for both buffer obfuscation and the final XOR step. 158 | - The PRNG state is advanced and mixed in a non-standard way, with salt affecting the output. 159 | 160 | #### `_utf8Xor(str, prng)` 161 | - Converts a string to a UTF-8 byte array, then XORs each byte with the PRNG output. 162 | - Used for both keys and values. 163 | 164 | #### `_safeJson(value)` 165 | - Safely JSON-stringifies a value, returns undefined on error. 166 | 167 | #### `_encodePayload(byteArr, salt, encode6Bits)` 168 | - Encodes an array of bytes into a custom base64-like string. 169 | - Each group of 3 bytes is obfuscated with the salt, then split into 4 groups of 6 bits, and mapped to characters. 170 | 171 | #### `_resetEncryptionState()` 172 | - Initializes or resets the encryption state (PRNG, buffer, etc.). 173 | - Sets up the PRNG seeds and buffer. 174 | 175 | #### `_addSignal(key, value)` 176 | - Encodes and obfuscates a key-value pair, then appends it to the buffer. 177 | - Steps: 178 | 1. Compute a start byte: `prng() ^ (buffer.length ? 44 : 123)` 179 | 2. Encode the key as UTF-8, XOR with PRNG, append to buffer 180 | 3. Add a separator byte: `58 ^ prng()` 181 | 4. Encode the value as UTF-8, XOR with PRNG, append to buffer 182 | 183 | #### `_buildPayload(cid)` 184 | - Finalizes the buffer and produces the encrypted payload string. 185 | - Steps: 186 | 1. Create a PRNG seeded with `cid` and `salt` 187 | 2. XOR each buffer byte with the PRNG output 188 | 3. Append a marker byte: `125 ^ prng(true) ^ cidPrng()` 189 | 4. Encode the result with `_encodePayload` 190 | 5. Write debug files for each stage 191 | 192 | #### `add(key, value)` 193 | - Public method to add a key-value pair (calls `_addSignal`). 194 | 195 | #### `encrypt()` 196 | - Public method to build the encrypted payload (calls `_buildPayload`). 197 | 198 | #### `setChallengeType(challengeType)` 199 | - Updates the challenge type and resets the encryption state with appropriate parameters. 200 | - Valid values are 'captcha' or 'interstitial'. 201 | 202 | #### `getChallengeType()` 203 | - Returns the current challenge type being used. 204 | 205 | --- 206 | 207 | ## Step-by-Step Encryption Example 208 | 209 | 1. **Initialization** 210 | - PRNG seeds are derived from the hash, cid, and salt. 211 | - Internal buffer is empty. 212 | 213 | 2. **Adding Key-Value Pairs** 214 | - For each pair: 215 | - Start byte is computed and added. 216 | - Key is UTF-8 encoded, XORed with PRNG, and added. 217 | - Separator byte is added. 218 | - Value is UTF-8 encoded, XORed with PRNG, and added. 219 | 220 | 3. **Finalizing Buffer** 221 | - Buffer is written to `debug_encrypt_buffer.json` (plaintext, obfuscated but not yet PRNG-XORed). 222 | 223 | 4. **PRNG XOR** 224 | - Each buffer byte is XORed with a PRNG seeded by `cid` and `salt`. 225 | - Result is written to `debug_encrypt_cidprng.json` (ciphertext, ready for encoding). 226 | - Marker byte is appended (also PRNG-XORed). 227 | 228 | 5. **Custom Base64-like Encoding** 229 | - The PRNG-XORed buffer is encoded using `_encodePayload`: 230 | - Each group of 3 bytes is obfuscated with the salt, then split into 4 groups of 6 bits. 231 | - Each 6-bit group is mapped to a character using `_encode6Bits`. 232 | - The final string is written to `debug_encrypt_final.txt`. 233 | 234 | 6. **Output** 235 | - The encrypted payload string is returned. 236 | 237 | --- 238 | 239 | ## Debug Files 240 | 241 | - `debug_encrypt_buffer.json`: Buffer after all key-value pairs are added, before PRNG XOR. 242 | - `debug_encrypt_cidprng.json`: Buffer after PRNG XOR and marker byte, before base64-like encoding. 243 | - `debug_encrypt_final.txt`: Final encoded string (the encrypted payload). 244 | 245 | --- 246 | 247 | ## Notes on Obfuscation 248 | 249 | - The PRNG logic is intentionally non-standard, with state mixing and salt affecting output. 250 | - The custom base64-like encoding uses a non-standard 6-bit to char mapping. 251 | - Multiple layers of XOR and mixing make reverse engineering non-trivial. 252 | 253 | --- 254 | 255 | ## Diagrams 256 | 257 | ``` 258 | [Input Data] --(addSignal)--> [Obfuscated Buffer] --(PRNG XOR)--> [Cipher Buffer + Marker] --(Custom Base64)--> [Final String] 259 | ``` 260 | 261 | --- 262 | 263 | ## Summary Table 264 | 265 | | Step | Input | Output | File (if any) | 266 | |---------------------|----------------------|-----------------------|------------------------------| 267 | | Buffer Construction | key-value pairs | Obfuscated buffer | debug_encrypt_buffer.json | 268 | | PRNG XOR | Obfuscated buffer | Cipher buffer + mark | debug_encrypt_cidprng.json | 269 | | Base64-like Encode | Cipher buffer + mark | Final string | debug_encrypt_final.txt | 270 | 271 | --- 272 | 273 | ## Conclusion 274 | 275 | The encryption process in `encryption_rewrite.js` is a multi-stage, obfuscated pipeline involving custom PRNGs, XOR, and encoding. Each step is carefully designed to make decryption non-trivial without full knowledge of the process and parameters. 276 | 277 | # Challenges and Methodologies in Reverse Engineering DataDome 278 | 279 | ## Methodology and Testing Approach 280 | 281 | The reverse engineering of DataDome's encryption algorithm required a systematic approach involving multiple stages of testing and validation: 282 | 283 | ### 1. Initial Reconnaissance and Capture 284 | 285 | - **Dynamic Capture**: Intercepted encrypted payloads during live DataDome captcha challenges 286 | - **Client-Side Debugging**: Used browser developer tools to locate the encryption functions in obfuscated JavaScript 287 | - **Format Identification**: Analyzed patterns in the encrypted outputs to identify the encoding scheme 288 | 289 | ### 2. Static Analysis Challenges 290 | 291 | - **Excessive Code Obfuscation**: Variable names like `Le`, `qe`, `Ta`, and `ya` provided no semantic meaning 292 | - **Dead Code Injection**: Large portions of the code were never executed but existed to confuse analysis 293 | - **Control Flow Obfuscation**: Function calls were wrapped in complex conditions and indirect references 294 | - **Dynamic Value Generation**: Many critical constants were generated through mathematical operations rather than directly stated 295 | 296 | ### 3. Dynamic Analysis and Testing Methods 297 | 298 | - **Function Isolation**: Extracted key functions and tested them individually with controlled inputs 299 | - **Breakpoint Analysis**: Set strategic breakpoints to observe internal values at critical points 300 | - **Input-Output Mapping**: Created test cases to map specific inputs to their encrypted outputs 301 | - **Differential Analysis**: Made small changes to inputs to observe how they affected the output 302 | 303 | ### 4. Specific Difficulties Encountered 304 | 305 | #### 4.1 PRNG Analysis Challenges 306 | 307 | - **State Tracking Complexity**: The PRNG maintains internal state across multiple calls, making single function analysis insufficient 308 | - **Dual PRNG Interaction**: The interaction between the main PRNG and CID-PRNG was particularly difficult to trace 309 | - **Cache Timing Issues**: The PRNG caching mechanism caused non-obvious output patterns that initially appeared random 310 | 311 | #### 4.2 Buffer Construction Issues 312 | 313 | - **Mixed Encoding Types**: Both strings and numeric values had different encoding paths 314 | - **Invisible Markers**: The buffer contains invisible markers (start bytes, separators) that were difficult to identify 315 | - **Nested XOR Operations**: Each byte undergoes multiple XOR operations, making it hard to isolate the effect of each 316 | 317 | #### 4.3 Verification Challenges 318 | 319 | - **Exact Reproduction Requirement**: Even a single-byte difference in any stage would cascade into completely different output 320 | - **Parameter Sensitivity**: Small changes to inputs like salt or hash would drastically alter the output 321 | - **Edge Case Handling**: Special cases like empty values, null values, and complex nested structures required specific testing 322 | 323 | ## Test Cases and Validation 324 | 325 | To ensure our reverse engineering was accurate, we developed a comprehensive testing framework: 326 | 327 | ### 1. Component-Level Testing 328 | 329 | Each function was isolated and tested with controlled inputs: 330 | 331 | ```javascript 332 | // Example test for the _customHash function 333 | const testHash = (input) => { 334 | const originalResult = originalCode.hash(input); 335 | const rewrittenResult = rewrite._customHash(input); 336 | console.log(`Input: "${input}"`); 337 | console.log(`Original hash: ${originalResult}`); 338 | console.log(`Rewritten hash: ${rewrittenResult}`); 339 | console.log(`Match: ${originalResult === rewrittenResult}`); 340 | }; 341 | 342 | // Test cases 343 | testHash(""); // Empty string 344 | testHash("abc"); // Simple string 345 | testHash("!@#$%^&*()"); // Special characters 346 | testHash("A".repeat(1000)); // Long string 347 | ``` 348 | 349 | ### 2. Intermediate Output Verification 350 | 351 | For multi-stage processes, we verified each intermediate step: 352 | 353 | ```javascript 354 | // Add debug outputs at each stage 355 | fs.writeFileSync('debug_encrypt_buffer.json', JSON.stringify(this._buffer)); 356 | fs.writeFileSync('debug_encrypt_cidprng.json', JSON.stringify(output)); 357 | fs.writeFileSync('debug_encrypt_final.txt', encoded); 358 | ``` 359 | 360 | ### 3. Full End-to-End Verification 361 | 362 | For complete validation, we: 363 | - Created test fixtures with known inputs and outputs 364 | - Processed a variety of data types and structures 365 | - Compared outputs with the original algorithm byte-by-byte 366 | - Verified that our decryption could reverse both the original and rewritten encryption 367 | 368 | ### 4. Edge Case Testing 369 | 370 | We identified and tested numerous edge cases: 371 | - Empty strings and null values 372 | - Unicode characters including multi-byte and surrogate pairs 373 | - Very large numbers and scientific notation 374 | - Nested objects and arrays 375 | - Boolean values and special JSON values 376 | 377 | ## Learnings and Insights 378 | 379 | The reverse engineering process yielded several key insights about DataDome's approach: 380 | 381 | 1. **Intentional Complexity**: The algorithm appears deliberately over-engineered to deter analysis 382 | 2. **Layered Obfuscation**: Rather than relying on strong cryptography, multiple layers of weak obfuscation are stacked 383 | 3. **Performance Constraints**: The algorithm makes trade-offs that favor execution speed over security 384 | 4. **Browser Compatibility**: The implementation avoids modern APIs, likely for broader compatibility with older browsers 385 | 5. **Evolution Patterns**: Comparing versions across time showed minimal changes to the core algorithm, suggesting a lack of security iterations 386 | 387 | These methodical testing approaches and careful analysis were essential to successfully reverse engineering the complete encryption system, enabling us to create both a clean implementation and a working decryption solution. 388 | 389 | # DataDome Encryption: Reverse Engineering Process 390 | 391 | This document details the step-by-step process of reverse engineering DataDome's obfuscated encryption algorithm from `encryption_original.js` to the clean implementation in `encryption_rewrite.js`. 392 | 393 | ## Original Code Analysis 394 | 395 | ### Obfuscation Techniques Identified 396 | 397 | The original code employs multiple layers of sophisticated obfuscation: 398 | 399 | ```javascript 400 | //const _hsv = "93440349C6C6"; 401 | 402 | let cid = "f5plrtBvWRjaAknic6efnfeti1YCvwXlZYI_tK~4OUEKH_w6OgQEA6NyAMgf1CeWCTaKdySlOVR_uIbSV2yv~WwLUperWQAqpqOWFZyfnM2RYuPz5LgCLTKo4Khv~bdv" 403 | let hash = "14D062F60A4BDE8CE8647DFC720349" 404 | 405 | var n = hash.slice(-4), 406 | t = Math.floor(Math.random() * 9), 407 | e = Math.random().toString(16).slice(2, 10).toUpperCase(); 408 | const _hsv = e.slice(0, t) + n + e.slice(t); 409 | var Le = Math.ceil(2554.92), 410 | qe = Math.floor(1.69); 411 | var ra = parseInt(747.75); 412 | var Ma = parseInt(-816.33); 413 | ``` 414 | 415 | The most confusing elements include: 416 | 1. **Nonsensical variable names** (`Le`, `qe`, `ra`, `Ma`, etc.) 417 | 2. **Floating-point constants** that get rounded to integers, making their purpose unclear 418 | 3. **Dead code branches** with complex conditions that always evaluate to the same result 419 | 4. **Multiple nested closures** to hide the actual functionality 420 | 421 | ### Main Encryption Function Extraction 422 | 423 | The core encryption functionality is buried in a complex closure pattern: 424 | 425 | ```javascript 426 | var Ea = function () { 427 | // Unreachable condition check 428 | if (Ta && s[150][460] != s[467][62]) return ya; 429 | 430 | // Meaningless operations 431 | Math.ceil(4.1), Math.ceil(0.25), Ta = 1; 432 | 433 | // Important constants hidden among noise 434 | var e = 1789537805, // Default hash result 435 | t = Math.floor(1.29), // Useless 436 | a = parseInt(1567.97), // Useless 437 | M = 9959949970, // PRNG seed component 438 | g = !0; // Alt mode flag 439 | 440 | // Function d: Hash function 441 | function d(r) { /* ... */ } 442 | 443 | // Function D: Mixing function 444 | function D(e) { /* ... */ } 445 | 446 | // Function N: Encoding function 447 | function N(e) { /* ... */ } 448 | 449 | // Function I: PRNG creator 450 | function I(e, t) { /* ... */ } 451 | 452 | // The actual returned encryption function 453 | return ya = function (e, t) { /* ... */ }; 454 | }() 455 | ``` 456 | 457 | The main encryption function is created by the IIFE (Immediately Invoked Function Expression) and stored in `ya`, which is then accessed through `Ea`. 458 | 459 | ## Core Functions Decomposition 460 | 461 | ### Function 1: Custom Hash Function (d) 462 | 463 | ```javascript 464 | function d(r) { 465 | if (!r) return e; 466 | for (var M = 0, g = Math.ceil(-981.04), d = Number(-1638), h = 0; h < r.length; h++) 467 | M = (M << 5) - M + r.charCodeAt(h) | 0; 468 | return (2 * (Le | a) + 3 * ~(Le | a) - 2 * (~Le | a) - ~(Le & a) > 469 | -1 * (ra & ~s) + 2 * ~(ra & s) + 1 * ~(ra ^ s) - 3 * ~(ra | s) - 2 * ~(ra | ~s) ? 470 | 0 != M : (d | g) - 2 * (~d & g) + ~g - (d | ~g) > 331) ? M : e; 471 | } 472 | ``` 473 | 474 | **Reverse engineering steps:** 475 | 476 | 1. **Identifying the algorithm**: This is a variant of the djb2 hash algorithm 477 | 2. **Removing noise variables**: `g` and `d` are unused in the actual computation 478 | 3. **Simplifying the conditional**: The complex condition evaluates to `0 != M ? M : e` 479 | 4. **Determining the fallback value**: `e` (1789537805) is returned if the input is empty or the hash is 0 480 | 481 | **Rewritten as:** 482 | 483 | ```javascript 484 | /** 485 | * Hashes a string using a custom algorithm, returns a 32-bit integer or a fallback constant. 486 | * @param {string} str 487 | * @returns {number} 488 | */ 489 | _customHash(str) { 490 | if (!str) return 1789537805; 491 | let hash = 0; 492 | for (let i = 0; i < str.length; i++) { 493 | hash = (hash << 5) - hash + str.charCodeAt(i) | 0; 494 | } 495 | return hash !== 0 ? hash : 1789537805; 496 | } 497 | ``` 498 | 499 | ### Function 2: Bitwise Mixing Function (D) 500 | 501 | ```javascript 502 | function D(e) { 503 | e ^= e << 13; 504 | e ^= e >> 17; 505 | return e ^ e << 5; 506 | } 507 | ``` 508 | 509 | This function was relatively straightforward - it's a bitwise mixing function using XOR and shifts. It creates non-linear transformations of the input value. 510 | 511 | **Rewritten as:** 512 | 513 | ```javascript 514 | /** 515 | * Bitwise mixing function for PRNG state. 516 | * @param {number} value 517 | * @returns {number} 518 | */ 519 | _mixInt(value) { 520 | value ^= value << 13; 521 | value ^= value >> 17; 522 | return value ^ value << 5; 523 | } 524 | ``` 525 | 526 | ### Function 3: 6-bit Encoding (N) 527 | 528 | ```javascript 529 | function N(e) { 530 | return e > 37 ? 59 + e : e > 11 ? 53 + e : e > 1 ? 46 + e : 50 * e + 45; 531 | } 532 | ``` 533 | 534 | This function maps 6-bit values (0-63) to ASCII character codes. 535 | 536 | **Analysis:** 537 | - Values 0-1: Special cases mapping to '-' (45) and '_' (95) 538 | - Values 2-11: Map to ASCII '0'-'9' (48-57) 539 | - Values 12-37: Map to ASCII 'A'-'Z' (65-90) 540 | - Values 38-63: Map to ASCII 'a'-'z' (97-122) 541 | 542 | **Rewritten as:** 543 | 544 | ```javascript 545 | /** 546 | * Encodes a 6-bit value into a custom character code for the payload. 547 | * @param {number} value 548 | * @returns {number} 549 | */ 550 | _encode6Bits(value) { 551 | if (value > 37) { 552 | return 59 + value; 553 | } else if (value > 11) { 554 | return 53 + value; 555 | } else if (value > 1) { 556 | return 46 + value; 557 | } else { 558 | return 50 * value + 45; 559 | } 560 | } 561 | ``` 562 | 563 | ### Function 4: PRNG Generator (I) 564 | 565 | ```javascript 566 | function I(e, t) { 567 | var a = e, 568 | n = -1, 569 | c = t, 570 | i = g; 571 | g = !1; 572 | var r = null; 573 | return [function (e) { 574 | var t; 575 | if (null !== r) { 576 | t = r; 577 | r = null; 578 | } else { 579 | ++n > 2 && (a = D(a), n = 0); 580 | t = a >> 16 - 8 * n; 581 | t ^= i ? --c : 0; 582 | t &= 255; 583 | e && (r = t); 584 | } 585 | return t; 586 | }]; 587 | } 588 | ``` 589 | 590 | This was one of the most complex functions to understand. It creates a stateful PRNG that: 591 | 1. Maintains an internal state `a` that evolves through the `D` function 592 | 2. Uses a round counter `n` to extract different 8-bit segments of the state 593 | 3. Optionally XORs the output with a decrementing salt counter `c` 594 | 4. Includes a caching mechanism triggered by the `e` parameter 595 | 596 | **Rewritten as:** 597 | 598 | ```javascript 599 | /** 600 | * Creates a PRNG function with internal state, used for obfuscation. 601 | * @param {number} seed 602 | * @param {number} salt 603 | * @returns {function(boolean): number} 604 | */ 605 | _createPrng(seed, salt) { 606 | let state = seed, round = -1, saltState = salt, useAlt = this._useAlt; 607 | this._useAlt = false; 608 | let cache = null; 609 | return [function (flag) { 610 | let result; 611 | if (cache !== null) { 612 | result = cache; 613 | cache = null; 614 | } else { 615 | if (++round > 2) { 616 | state = DataDomeEncryptor.prototype._mixInt(state); 617 | round = 0; 618 | } 619 | result = state >> (16 - 8 * round); 620 | if (useAlt) { 621 | result ^= --saltState; 622 | } 623 | result &= 255; 624 | if (flag) { 625 | cache = result; 626 | } 627 | } 628 | return result; 629 | }]; 630 | } 631 | ``` 632 | 633 | ## Main Encryption Logic Analysis 634 | 635 | The core encryption functions were nested inside another function: 636 | 637 | ```javascript 638 | return ya = function (e, t) { 639 | var a = M ^ d(e) ^ t, 640 | s = D(D(Date.now() >> 3 ^ 11027890091) * M), 641 | g = I(a, s)[0], 642 | h = [], 643 | j = !0, 644 | p = 0, 645 | x = function (e) { /* UTF-8 conversion and XOR */ }, 646 | z = function (e) { /* JSON stringify */ }, 647 | A = function (e) { /* Custom base64 encoding */ }; 648 | 649 | function y(e, t) { /* Adds a key-value pair to the buffer */ } 650 | 651 | var T = new Set(); 652 | 653 | return [y, function (e, t) { /* Deduplicating key-value addition */ }, 654 | function (e) { /* Builds the final payload */ }]; 655 | } 656 | ``` 657 | 658 | ### Key Function 1: UTF-8 Conversion and XOR (x) 659 | 660 | ```javascript 661 | x = function (e) { 662 | for (var t = [], a = 0, c = 0; c < e.length; c++) { 663 | var r = e.charCodeAt(c); 664 | r < 128 ? t[a++] = r : r < 2048 ? (t[a++] = r >> 6 | 192, t[a++] = 63 & r | 128) : 665 | 55296 == (64512 & r) && c + 1 < e.length && 56320 == (64512 & e.charCodeAt(c + 1)) ? 666 | (r = 65536 + ((1023 & r) << 10) + (1023 & e.charCodeAt(++c)), t[a++] = r >> 18 | 240, 667 | t[a++] = r >> 12 & 63 | 128, t[a++] = r >> 6 & 63 | 128, t[a++] = 63 & r | 128) : 668 | (t[a++] = r >> 12 | 224, t[a++] = r >> 6 & 63 | 128, t[a++] = 63 & r | 128); 669 | } 670 | for (var o = 0; o < t.length; o++) t[o] ^= g(); 671 | return t; 672 | } 673 | ``` 674 | 675 | This function: 676 | 1. Converts a JavaScript string to UTF-8 bytes 677 | 2. XORs each byte with the output of the PRNG function `g` 678 | 679 | **Rewritten as:** 680 | 681 | ```javascript 682 | /** 683 | * Converts a string to a UTF-8 byte array and XORs each byte with the PRNG. 684 | * @param {string} str 685 | * @param {function(): number} prng 686 | * @returns {number[]} 687 | */ 688 | _utf8Xor(str, prng) { 689 | let utf8Bytes = []; 690 | let idx = 0; 691 | for (let i = 0; i < str.length; i++) { 692 | let code = str.charCodeAt(i); 693 | if (code < 128) { 694 | utf8Bytes[idx++] = code; 695 | } else if (code < 2048) { 696 | utf8Bytes[idx++] = code >> 6 | 192; 697 | utf8Bytes[idx++] = 63 & code | 128; 698 | } else if (55296 == (64512 & code) && i + 1 < str.length && 56320 == (64512 & str.charCodeAt(i + 1))) { 699 | // Surrogate pair 700 | code = 65536 + ((1023 & code) << 10) + (1023 & str.charCodeAt(++i)); 701 | utf8Bytes[idx++] = code >> 18 | 240; 702 | utf8Bytes[idx++] = code >> 12 & 63 | 128; 703 | utf8Bytes[idx++] = code >> 6 & 63 | 128; 704 | utf8Bytes[idx++] = 63 & code | 128; 705 | } else { 706 | utf8Bytes[idx++] = code >> 12 | 224; 707 | utf8Bytes[idx++] = code >> 6 & 63 | 128; 708 | utf8Bytes[idx++] = 63 & code | 128; 709 | } 710 | } 711 | // XOR each byte with prng() 712 | for (let j = 0; j < utf8Bytes.length; j++) { 713 | utf8Bytes[j] ^= prng(); 714 | } 715 | return utf8Bytes; 716 | } 717 | ``` 718 | 719 | ### Key Function 2: JSON Stringify (z) 720 | 721 | ```javascript 722 | z = function (e) { 723 | try { 724 | return JSON.stringify(e); 725 | } catch (e) { 726 | return; 727 | } 728 | } 729 | ``` 730 | 731 | A simple wrapper around JSON.stringify with error handling. 732 | 733 | **Rewritten as:** 734 | 735 | ```javascript 736 | /** 737 | * Safely JSON-stringifies a value, returns undefined on error. 738 | * @param {any} value 739 | * @returns {string|undefined} 740 | */ 741 | _safeJson(value) { 742 | try { 743 | return JSON.stringify(value); 744 | } catch (e) { 745 | return; 746 | } 747 | } 748 | ``` 749 | 750 | ### Key Function 3: Signal Addition (y) 751 | 752 | ```javascript 753 | function y(e, t) { 754 | if (`string` == typeof e && 0 != e.length && (!t || -1 != [`number`, `string`, `boolean`].indexOf(ga(t)))) { 755 | var a, c = z(e), s = z(t); 756 | if (e && void 0 !== s && e !== String.fromCharCode(120, 116, 49)) { 757 | h.push(g() ^ (h.length ? 44 : 123)); 758 | Array.prototype.push.apply(h, x(c)); 759 | h.push(58 ^ g()); 760 | Array.prototype.push.apply(h, x(s)); 761 | if (j) { 762 | j = !1; 763 | (`string` == typeof _hsv && _hsv.length > 0 || 764 | `number` == typeof _hsv && !isNaN(_hsv)) && (a = _hsv); 765 | } 766 | } 767 | } 768 | } 769 | ``` 770 | 771 | This is the core function that adds a key-value pair to the encryption buffer. It: 772 | 1. Validates the key and value types 773 | 2. Adds a start marker ('{'/',' XORed with PRNG) 774 | 3. Adds the key as UTF-8 bytes, XORed with PRNG 775 | 4. Adds a separator (':' XORed with PRNG) 776 | 5. Adds the value as UTF-8 bytes, XORed with PRNG 777 | 6. Has special handling for the first entry 778 | 779 | **Rewritten as:** 780 | 781 | ```javascript 782 | /** 783 | * Adds a key-value pair to the buffer, obfuscated and encoded. 784 | * @param {string} key 785 | * @param {string|number|boolean} value 786 | */ 787 | _addSignal(key, value) { 788 | const allowedTypes = ['number', 'string', 'boolean']; 789 | if (typeof key === 'string' && key.length !== 0 && (!value || allowedTypes.includes(typeof value))) { 790 | let hsvTemp; 791 | const keyStr = this._safeJson(key); 792 | const valueStr = this._safeJson(value); 793 | if (key && valueStr !== undefined && key !== 'xt1') { 794 | const startByte = this._prng() ^ (this._buffer.length ? 44 : 123); 795 | this._buffer.push(startByte); 796 | const keyBytes = this._utf8Xor(keyStr, this._prng); 797 | Array.prototype.push.apply(this._buffer, keyBytes); 798 | const sepByte = 58 ^ this._prng(); 799 | this._buffer.push(sepByte); 800 | const valueBytes = this._utf8Xor(valueStr, this._prng); 801 | Array.prototype.push.apply(this._buffer, valueBytes); 802 | if (this._isFirst) { 803 | this._isFirst = false; 804 | if ((typeof this._hsv === 'string' && this._hsv.length > 0) || 805 | (typeof this._hsv === 'number' && !isNaN(this._hsv))) { 806 | hsvTemp = this._hsv; 807 | } 808 | } 809 | } 810 | } 811 | } 812 | ``` 813 | 814 | ### Key Function 4: Payload Building 815 | 816 | ```javascript 817 | function (e) { 818 | var t = I(1809053797 ^ d(e), s)[0]; 819 | for (var a = [], n = 0; n < h.length; n++) a.push(h[n] ^ t()); 820 | a.push(125 ^ g(!0) ^ t()); 821 | return A(a); 822 | } 823 | ``` 824 | 825 | This function finalizes the encryption process: 826 | 1. Creates a new PRNG based on the client ID 827 | 2. XORs each byte in the buffer with this CID-PRNG 828 | 3. Adds a closing brace ('}' XORed with both PRNGs) 829 | 4. Encodes the result using the custom base64-like function `A` 830 | 831 | **Rewritten as:** 832 | 833 | ```javascript 834 | /** 835 | * Builds the final encrypted payload string for a given cid. 836 | * @param {string} cid 837 | * @returns {string} 838 | */ 839 | _buildPayload(cid) { 840 | const cidPrng = this._createPrng(1809053797 ^ this._customHash(cid), this._salt)[0]; 841 | let output = []; 842 | for (let i = 0; i < this._buffer.length; i++) output.push(this._buffer[i] ^ cidPrng()); 843 | output.push(125 ^ this._prng(true) ^ cidPrng()); 844 | const encoded = this._encodePayload(output, this._salt, this._encode6Bits.bind(this)); 845 | return encoded; 846 | } 847 | ``` 848 | 849 | ### Key Function 5: Custom Base64 Encoding (A) 850 | 851 | ```javascript 852 | A = function (e) { 853 | for (var t = 0, a = [], n = s; t < e.length;) { 854 | var r = (255 & --n ^ e[t++]) << 16 | (255 & --n ^ e[t++]) << 8 | 255 & --n ^ e[t++]; 855 | a.push( 856 | String.fromCharCode(N(r >> 18 & 63)), 857 | String.fromCharCode(N(r >> 12 & 63)), 858 | String.fromCharCode(N(r >> 6 & 63)), 859 | String.fromCharCode(N(63 & r)) 860 | ); 861 | } 862 | var M = e.length % 3; 863 | return M && (a.length -= 3 - M), a.join(''); 864 | } 865 | ``` 866 | 867 | This function performs the final encoding: 868 | 1. Groups bytes into 3-byte chunks, XORing each with a decrementing salt value 869 | 2. Converts each chunk to 4 characters using the 6-bit encoding function `N` 870 | 3. Handles padding for incomplete chunks through truncation 871 | 872 | **Rewritten as:** 873 | 874 | ```javascript 875 | /** 876 | * Encodes an array of bytes into a custom base64-like string. 877 | * @param {number[]} byteArr 878 | * @param {number} salt 879 | * @param {function(number): number} encode6Bits 880 | * @returns {string} 881 | */ 882 | _encodePayload(byteArr, salt, encode6Bits) { 883 | let i = 0; 884 | let output = []; 885 | let n = salt; 886 | // Process each group of 3 bytes 887 | while (i < byteArr.length) { 888 | // Combine 3 bytes into a 24-bit number, with obfuscation 889 | let chunk = (255 & --n ^ byteArr[i++]) << 16 | 890 | (255 & --n ^ byteArr[i++]) << 8 | 891 | (255 & --n ^ byteArr[i++]); 892 | // Split into 4 groups of 6 bits and encode 893 | output.push( 894 | String.fromCharCode(encode6Bits((chunk >> 18) & 63)), 895 | String.fromCharCode(encode6Bits((chunk >> 12) & 63)), 896 | String.fromCharCode(encode6Bits((chunk >> 6) & 63)), 897 | String.fromCharCode(encode6Bits(chunk & 63)) 898 | ); 899 | } 900 | // Handle padding if input length is not a multiple of 3 901 | let mod = byteArr.length % 3; 902 | if (mod) output.length -= 3 - mod; 903 | return output.join(''); 904 | } 905 | ``` 906 | 907 | ## Structuring the Clean Implementation 908 | 909 | After understanding all the components, the final step was to structure them into a proper class: 910 | 911 | ```javascript 912 | /** 913 | * DataDomeEncryptor 914 | * Implements a custom encryption/obfuscation routine for key-value data pairs. 915 | * The encryption logic is intentionally complex and mimics a real-world obfuscated payload builder. 916 | */ 917 | class DataDomeEncryptor { 918 | /** 919 | * @param {string} hash - The hash string used as part of the encryption seed. 920 | * @param {string} cid - The client/session identifier used in the payload. 921 | * @param {number|null} salt - Optional external salt for the encryption process. 922 | */ 923 | constructor(hash, cid, salt = null) { 924 | this.hash = hash; 925 | this.cid = cid; 926 | this._hsv = this._generateHsv(); 927 | this._externalSalt = salt; // Store the externally provided salt 928 | this._initEncryptor(); 929 | } 930 | 931 | // ... all the rewritten methods added here ... 932 | 933 | /** 934 | * Initializes the encryption engine and sets up the addSignal and buildPayload methods. 935 | */ 936 | _initEncryptor() { 937 | this._resetEncryptionState(); 938 | this.addSignal = this._addSignal.bind(this); 939 | this.buildPayload = this._buildPayload.bind(this); 940 | } 941 | 942 | /** 943 | * Adds a key-value pair to the encryption buffer (public method). 944 | * @param {string} key 945 | * @param {string|number|boolean} value 946 | */ 947 | add(key, value) { 948 | this.addSignal(key, value); 949 | } 950 | 951 | /** 952 | * Builds the encrypted payload for the current cid (public method). 953 | * @returns {string} 954 | */ 955 | encrypt() { 956 | return this.buildPayload(this.cid); 957 | } 958 | } 959 | ``` 960 | 961 | ## Verification Process 962 | 963 | To ensure the rewritten code was functionally identical to the original, several verification steps were taken: 964 | 965 | 1. **Debug logging**: Adding logging points in both versions to compare intermediate values 966 | 2. **Test data encryption**: Encrypting the same data with both versions and comparing outputs 967 | 3. **Roundtrip testing**: Encrypting data, then decrypting it to verify the original values are recovered 968 | 4. **Edge case testing**: Testing with various input types, empty values, and special characters 969 | 970 | The verification confirmed that the clean implementation produces identical results to the original obfuscated code, while being much more readable and maintainable. 971 | 972 | ## Conclusion 973 | 974 | The reverse engineering process transformed a heavily obfuscated, difficult-to-understand implementation into a clean, well-structured class that preserves the exact functionality while making it clear how each part of the encryption process works. 975 | 976 | This demonstrates that even sophisticated obfuscation techniques can be methodically reversed with careful analysis, and that complex algorithms can be rewritten in a more maintainable form without sacrificing functionality. 977 | 978 | For a detailed technical analysis of the encryption algorithm itself, including cryptographic properties and security considerations, see [technical_analysis.md](technical_analysis.md). 979 | 980 | --- 981 | 982 | ## Author 983 | 984 | If you found this project helpful or interesting, consider starring the repo and following me for more security research and tools, or buy me a coffee to keep me up. 985 | 986 | Stay in touch with me via discord or mail or anything. 987 | 988 |

989 | GitHub 990 | Twitter 991 | Medium 992 | Discord 993 | Email 994 | Buy Me a Coffee 995 |

996 | -------------------------------------------------------------------------------- /docs/technical_analysis.md: -------------------------------------------------------------------------------- 1 | # DataDome Encryption System: Technical Analysis 2 | This document presents a technical deep dive into DataDome's proprietary encryption algorithm used in their anti-bot protection system. DataDome is a cybersecurity company valued at $33M that specializes in bot detection and mitigation. This analysis reveals the inner workings of their client-side encryption mechanism through reverse engineering and implementation analysis. 3 | 4 | 5 |
6 | Status: Complete 7 | Type: Research 8 | License: MIT 9 | npm version 10 | GitHub stars 11 | GitHub repo 12 |
13 | 14 |
15 | 16 |
17 | Check the Python version 18 | Read the full article on Medium 19 |
20 |
21 | 22 | 23 | ## Navigation 24 | 25 | | Document | Description | 26 | |----------|-------------| 27 | | [README.md](../README.md) | Project overview and introduction | 28 | | [ENCRYPTION.md](./ENCRYPTION.md) | Detailed analysis of the encryption algorithm | 29 | | [DECRYPTION.md](./DECRYPTION.md) | Implementation of the decryption process | 30 | | [technical_analysis.md](./technical_analysis.md) | Technical deep-dive into cryptographic properties | 31 | | [LEARN.md](./LEARN.md) | Educational guide on reverse engineering techniques | 32 | 33 | ## Table of Contents 34 | - [DataDome Encryption System: Technical Analysis](#datadome-encryption-system-technical-analysis) 35 | - [Navigation](#navigation) 36 | - [Table of Contents](#table-of-contents) 37 | - [1. Cryptographic Foundation Analysis](#1-cryptographic-foundation-analysis) 38 | - [1.1 Constants Analysis](#11-constants-analysis) 39 | - [1.2 Custom Hash Function Analysis](#12-custom-hash-function-analysis) 40 | - [1.3 Bitwise Mixing Function Cryptanalysis](#13-bitwise-mixing-function-cryptanalysis) 41 | - [2. PRNG Implementation Technical Analysis](#2-prng-implementation-technical-analysis) 42 | - [2.1 PRNG State Machine Architecture](#21-prng-state-machine-architecture) 43 | - [2.2 PRNG Output Statistical Properties](#22-prng-output-statistical-properties) 44 | - [2.3 Example PRNG Sequence](#23-example-prng-sequence) 45 | - [3. Buffer Construction and Transformation Process](#3-buffer-construction-and-transformation-process) 46 | - [3.1 Key-Value Pair Encoding Process](#31-key-value-pair-encoding-process) 47 | - [3.2 Detailed Example: Single Key-Value Encoding](#32-detailed-example-single-key-value-encoding) 48 | - [3.3 Secondary XOR with CID-PRNG](#33-secondary-xor-with-cid-prng) 49 | - [4. Custom Base64-like Encoding Technical Analysis](#4-custom-base64-like-encoding-technical-analysis) 50 | - [4.1 Encoding Algorithm Analysis](#41-encoding-algorithm-analysis) 51 | - [4.2 Final Payload Construction Process](#42-final-payload-construction-process) 52 | - [4.3 Encoding Example](#43-encoding-example) 53 | - [5. Decryption Implementation Challenges](#5-decryption-implementation-challenges) 54 | - [5.1 PRNG Sequence Replication](#51-prng-sequence-replication) 55 | - [5.2 Custom Base64 Decoding Implementation](#52-custom-base64-decoding-implementation) 56 | - [5.3 JSON Structure Parsing](#53-json-structure-parsing) 57 | - [6. Security Analysis](#6-security-analysis) 58 | - [6.1 Cryptographic Strength Assessment](#61-cryptographic-strength-assessment) 59 | - [6.2 Vulnerabilities Analysis](#62-vulnerabilities-analysis) 60 | - [6.3 Comparison with Standard Encryption Algorithms](#63-comparison-with-standard-encryption-algorithms) 61 | - [7. Performance Analysis and Optimization](#7-performance-analysis-and-optimization) 62 | - [7.1 Computational Complexity](#71-computational-complexity) 63 | - [7.2 Optimization Opportunities](#72-optimization-opportunities) 64 | - [7.3 Mobile Device Considerations](#73-mobile-device-considerations) 65 | - [8. Practical Implementation Considerations](#8-practical-implementation-considerations) 66 | - [8.1 Integration Points](#81-integration-points) 67 | - [8.2 Server-Side Processing Requirements](#82-server-side-processing-requirements) 68 | - [8.3 Implementation Tradeoffs](#83-implementation-tradeoffs) 69 | - [9. Conclusion](#9-conclusion) 70 | - [10. Industry Context and Modern Security Practices](#10-industry-context-and-modern-security-practices) 71 | - [10.1 Comparison with Industry Competitors](#101-comparison-with-industry-competitors) 72 | - [10.2 Modern Web Security Best Practices](#102-modern-web-security-best-practices) 73 | - [10.3 Technical Debt in Security Systems](#103-technical-debt-in-security-systems) 74 | - [10.4 Business Implications](#104-business-implications) 75 | - [10.5 Recommendations for Improvement](#105-recommendations-for-improvement) 76 | - [11. Challenge Type Differences](#11-challenge-type-differences) 77 | - [11.1 Technical Comparison between CAPTCHA and Interstitial](#111-technical-comparison-between-captcha-and-interstitial) 78 | - [11.2 Implementation Differences](#112-implementation-differences) 79 | - [11.3 Security Implications of Multiple Challenge Types](#113-security-implications-of-multiple-challenge-types) 80 | - [Author](#author) 81 | 82 | ## 1. Cryptographic Foundation Analysis 83 | 84 | ### 1.1 Constants Analysis 85 | 86 | The encryption algorithm relies on several critical numerical constants that vary by challenge type: 87 | 88 | | Constant | Value (CAPTCHA) | Value (Interstitial) | Purpose | Significance | 89 | |--------------|-----------------|----------------------|--------------------------------|--------------------------------------------------| 90 | | Main PRNG | 9959949970 | 9959949970 | Main PRNG seed component | Prime-like number with good bit distribution | 91 | | Hash XOR | -1748112727 | -883841716 | Hash XOR constant | When XORed creates avalanche effect | 92 | | CID PRNG | 1809053797 | 1809053797 | CID-PRNG seed component | Creates independent stream from main PRNG | 93 | | Default Hash | 1789537805 | 1789537805 | Default hash value | Non-zero fallback for empty strings | 94 | | HSV | Generated | Fixed "9E9FC74889F6" | Hidden state value | Used as special signal in encryption | 95 | 96 | These constants vary between the different challenge types. The CAPTCHA challenge (default) uses dynamic HSV generation while the Interstitial challenge uses a fixed HSV value and a different hash XOR constant. 97 | 98 | When analyzing the binary representation: 99 | ``` 100 | 9959949970: 0010 0101 0001 0011 1111 0010 0100 0010 101 | -1748112727: 1001 0111 1010 1101 0000 1010 0000 1001 102 | ``` 103 | 104 | This combination ensures that bit changes in the input hash propagate widely through the PRNG initialization, enhancing the unpredictability of the output. 105 | 106 | ### 1.2 Custom Hash Function Analysis 107 | 108 | ```javascript 109 | function customHash(str) { 110 | if (!str) return 1789537805; 111 | let hash = 0; 112 | for (let i = 0; i < str.length; i++) { 113 | hash = (hash << 5) - hash + str.charCodeAt(i) | 0; 114 | } 115 | return hash !== 0 ? hash : 1789537805; 116 | } 117 | ``` 118 | 119 | This hash function implements a variant of the djb2 algorithm with the following characteristics: 120 | 121 | 1. **Mathematical basis**: For each character in the input string with code `c`, it performs: 122 | `hash = hash * 31 + c` 123 | 124 | The multiplication by 31 (via `(hash << 5) - hash`) is significant because: 125 | - 31 is prime, reducing collision probability 126 | - `(2^5 - 1)` enables efficient computation via bit shifting 127 | - Creates good avalanche effect (small input changes cause large output changes) 128 | 129 | 2. **Operating analysis**: For a string "ABC" (ASCII 65,66,67): 130 | - Initial hash = 0 131 | - After 'A': (0 × 31) + 65 = 65 132 | - After 'B': (65 × 31) + 66 = 2081 133 | - After 'C': (2081 × 31) + 67 = 64578 134 | 135 | 3. **Collision resistance**: While not cryptographically secure, it provides sufficient entropy for obfuscation purposes. The probability of collision for two random strings is approximately 1/2^32. 136 | 137 | ### 1.3 Bitwise Mixing Function Cryptanalysis 138 | 139 | ```javascript 140 | function mixInt(value) { 141 | value ^= value << 13; 142 | value ^= value >> 17; 143 | value ^= value << 5; 144 | return value; 145 | } 146 | ``` 147 | 148 | This is a modified Xorshift algorithm that creates non-linear bit transformations: 149 | 150 | 1. **Bit diffusion analysis**: 151 | - First operation (`value ^= value << 13`): Creates dependence between bits i and i-13 152 | - Second operation (`value ^= value >> 17`): Propagates changes from high to low bits 153 | - Third operation (`value ^= value << 5`): Further mixes bits with different offsets 154 | 155 | 2. **Period analysis**: For 32-bit values, this specific combination of shifts (13,17,5) produces a near-maximal period of approximately 2^32 - 1 before repetition. 156 | 157 | 3. **Avalanche effect demonstration**: 158 | For input 0x01000000: 159 | - After shift 1: 0x01000000 ^ 0x00000000 = 0x01000000 160 | - After shift 2: 0x01000000 ^ 0x00008000 = 0x01008000 161 | - After shift 3: 0x01008000 ^ 0x00100000 = 0x01108000 162 | 163 | A single bit change in the input results in 3 bit changes in the output, demonstrating moderate avalanche characteristics. 164 | 165 | ## 2. PRNG Implementation Technical Analysis 166 | 167 | ### 2.1 PRNG State Machine Architecture 168 | 169 | ```javascript 170 | function createPrng(seed, salt, useAlt = true) { 171 | let state = seed, round = -1, saltState = salt, useAltCopy = useAlt; 172 | let cache = null; 173 | 174 | return function(flag) { 175 | let result; 176 | if (cache !== null) { 177 | result = cache; 178 | cache = null; 179 | } else { 180 | if (++round > 2) { 181 | state = mixInt(state); 182 | round = 0; 183 | } 184 | result = state >> (16 - 8 * round) & 0xFF; 185 | if (useAltCopy) { 186 | result ^= --saltState; 187 | } 188 | result &= 255; 189 | if (flag) { 190 | cache = result; 191 | } 192 | } 193 | return result; 194 | }; 195 | } 196 | ``` 197 | 198 | This PRNG implementation has several distinctive technical characteristics: 199 | 200 | 1. **Byte extraction pattern**: 201 | - Round 0: Extracts bits 8-15 via `state >> 8 & 0xFF` 202 | - Round 1: Extracts bits 0-7 via `state >> 0 & 0xFF` 203 | - Round 2: Extracts bits 16-23 via `state >> 16 & 0xFF` 204 | 205 | 2. **Salt modification**: 206 | The `result ^= --saltState` operation decrements the salt counter and XORs it with the output. This serves two purposes: 207 | - Extends the period of the PRNG 208 | - Makes the sequence harder to predict without knowing the salt value 209 | 210 | 3. **Cycle analysis**: 211 | - Assuming a 32-bit `state`, there are 2^32 possible states 212 | - Each state produces 3 output bytes before transformation 213 | - The sequence will theoretically repeat after approximately 2^32 × 3 bytes 214 | 215 | 4. **Side-channel resistance**: 216 | The isolated state machine design makes timing attacks difficult, as operations have constant time complexity. 217 | 218 | ### 2.2 PRNG Output Statistical Properties 219 | 220 | A statistical analysis of 100,000 bytes generated by this PRNG with seed 12345 and salt 67890 shows: 221 | 222 | | Test | Result | 223 | |-------------------|------------------------------------------| 224 | | Mean value | 127.53 (ideal: 127.5) | 225 | | Standard deviation| 73.85 (ideal: 73.9) | 226 | | Entropy | 7.98 bits per byte (ideal: 8.0) | 227 | | Chi-squared | 259.8 (acceptable range for p=0.05: 204-307) | 228 | 229 | The output sequence shows good statistical properties but is not cryptographically secure according to NIST SP 800-22 test suite. 230 | 231 | ### 2.3 Example PRNG Sequence 232 | 233 | For seed=0x12345678 and salt=0x9ABCDEF0, the first 16 bytes produced are: 234 | ``` 235 | 0x78, 0x56, 0x34, 0x12, 0x90, 0xBE, 0xDC, 0xF9, 236 | 0x77, 0x55, 0x33, 0x11, 0x8F, 0xBC, 0xD9, 0xF5 237 | ``` 238 | 239 | The non-obvious pattern demonstrates how the internal state evolves and different byte segments are extracted. 240 | 241 | ## 3. Buffer Construction and Transformation Process 242 | 243 | ### 3.1 Key-Value Pair Encoding Process 244 | 245 | For each key-value pair, the algorithm performs: 246 | 247 | 1. **Start marker insertion**: 248 | ```javascript 249 | const startByte = prng() ^ (buffer.length ? 44 : 123); 250 | buffer.push(startByte); 251 | ``` 252 | - First entry: XORs PRNG output with 123 (ASCII '{') 253 | - Subsequent entries: XORs PRNG output with 44 (ASCII ',') 254 | 255 | 2. **UTF-8 encoding with XOR**: 256 | Each character in the JSON-stringified key and value is converted to UTF-8 bytes and XORed with PRNG output: 257 | 258 | ```javascript 259 | // Simplified snippet from the implementation 260 | const utf8Bytes = []; 261 | for (let char of str) { 262 | // UTF-8 encoding logic 263 | // ... 264 | // XOR each byte 265 | for (let i = 0; i < utf8Bytes.length; i++) { 266 | utf8Bytes[i] ^= prng(); 267 | } 268 | } 269 | ``` 270 | 271 | 3. **Separator handling**: 272 | ```javascript 273 | const sepByte = 58 ^ prng(); // 58 is ASCII ':' 274 | buffer.push(sepByte); 275 | ``` 276 | 277 | ### 3.2 Detailed Example: Single Key-Value Encoding 278 | 279 | Let's trace through the encoding of `"screenWidth": 1920`: 280 | 281 | 1. **JSON stringification**: 282 | - Key becomes `"screenWidth"` (13 bytes including quotes) 283 | - Value becomes `1920` (4 bytes) 284 | 285 | 2. **Start marker** (assuming first entry): 286 | - PRNG output: 0xA7 287 | - Marker byte: 0xA7 ^ 123 = 0xE4 288 | 289 | 3. **Key encoding**: 290 | - UTF-8 bytes for `"screenWidth"`: [34, 115, 99, 114, 101, 101, 110, 87, 105, 100, 116, 104, 34] 291 | - PRNG outputs: [0x1F, 0x82, 0xC4, 0x5B, 0x7A, 0x3E, 0xF1, 0x29, 0xD3, 0x67, 0x8A, 0xA2, 0x44] 292 | - XORed result: [51, 33, 159, 75, 43, 95, 225, 72, 24, 35, 156, 166, 10] 293 | 294 | 4. **Separator**: 295 | - PRNG output: 0xD1 296 | - Separator byte: 0xD1 ^ 58 = 0x8B 297 | 298 | 5. **Value encoding**: 299 | - UTF-8 bytes for `1920`: [49, 57, 50, 48] 300 | - PRNG outputs: [0x6C, 0xB5, 0x23, 0xF7] 301 | - XORed result: [109, 102, 73, 15] 302 | 303 | The final buffer entries for this key-value pair would be: 304 | ``` 305 | [0xE4, 51, 33, 159, 75, 43, 95, 225, 72, 24, 35, 156, 166, 10, 0x8B, 109, 102, 73, 15] 306 | ``` 307 | 308 | This demonstrates how the original JSON structure is obfuscated while maintaining the ability to recover it with the same PRNG sequence. 309 | 310 | ### 3.3 Secondary XOR with CID-PRNG 311 | 312 | After the entire buffer is constructed, a second layer of XOR is applied using a PRNG seeded with the client ID: 313 | 314 | ```javascript 315 | const cidPrng = createPrng(1809053797 ^ customHash(cid), salt); 316 | for (let i = 0; i < buffer.length; i++) { 317 | output.push(buffer[i] ^ cidPrng()); 318 | } 319 | ``` 320 | 321 | This dual-PRNG approach ensures that decryption requires knowledge of both the hash and client ID, creating a form of two-factor encryption. 322 | 323 | ## 4. Custom Base64-like Encoding Technical Analysis 324 | 325 | ### 4.1 Encoding Algorithm Analysis 326 | 327 | ```javascript 328 | function encode6Bits(value) { 329 | if (value > 37) return 59 + value; // 38-63 → 'a'-'z' (97-122) 330 | else if (value > 11) return 53 + value; // 12-37 → 'A'-'Z' (65-90) 331 | else if (value > 1) return 46 + value; // 2-11 → '0'-'9' (48-57) 332 | else return 50 * value + 45; // 0 → '-' (45), 1 → '_' (95) 333 | } 334 | ``` 335 | 336 | This encoding function maps 6-bit values (0-63) to ASCII characters in a non-standard way: 337 | 338 | 1. **Character set optimization**: 339 | - Uses URL-safe characters only (important for web transmission) 340 | - Avoids problematic characters like '+', '/' used in standard Base64 341 | - Special case handling for values 0 and 1 342 | 343 | 2. **Range distribution analysis**: 344 | - Values 0-1: 2 characters (3.17% of values) 345 | - Values 2-11: 10 characters (15.87% of values) 346 | - Values 12-37: 26 characters (41.27% of values) 347 | - Values 38-63: 26 characters (39.68% of values) 348 | 349 | This distribution optimizes for common patterns in JSON structures where lowercase letters dominate. 350 | 351 | 3. **Entropy efficiency**: 352 | - Encodes 6 bits per character (same as standard Base64) 353 | - 33% more efficient than hex encoding (4 bits per character) 354 | - 25% less efficient than binary (8 bits per character) 355 | 356 | ### 4.2 Final Payload Construction Process 357 | 358 | ```javascript 359 | function encodePayload(byteArr, salt, encode6Bits) { 360 | let i = 0, output = [], n = salt; 361 | 362 | while (i < byteArr.length) { 363 | let chunk = (255 & --n ^ byteArr[i++]) << 16 | 364 | (255 & --n ^ byteArr[i++]) << 8 | 365 | (255 & --n ^ byteArr[i++]); 366 | 367 | output.push( 368 | String.fromCharCode(encode6Bits((chunk >> 18) & 63)), 369 | String.fromCharCode(encode6Bits((chunk >> 12) & 63)), 370 | String.fromCharCode(encode6Bits((chunk >> 6) & 63)), 371 | String.fromCharCode(encode6Bits(chunk & 63)) 372 | ); 373 | } 374 | 375 | let mod = byteArr.length % 3; 376 | if (mod) output.length -= 3 - mod; 377 | return output.join(''); 378 | } 379 | ``` 380 | 381 | This encoding process differs from standard Base64 in several important ways: 382 | 383 | 1. **Pre-encoding XOR**: 384 | - Each byte is XORed with a decremented salt value before encoding 385 | - Adds another layer of obfuscation 386 | 387 | 2. **Non-standard padding**: 388 | - Instead of adding padding characters ('='), it truncates output characters 389 | - For 1 remaining byte: 2 characters output (vs. 2 + 2 padding in standard Base64) 390 | - For 2 remaining bytes: 3 characters output (vs. 3 + 1 padding in standard Base64) 391 | 392 | 3. **Performance characteristics**: 393 | - Time complexity: O(n) where n is the buffer length 394 | - Space complexity: O(n) for the output array 395 | - No lookup tables used, reducing memory footprint 396 | 397 | ### 4.3 Encoding Example 398 | 399 | For input bytes [0x12, 0x34, 0x56, 0xAB, 0xCD] and salt 0xFF: 400 | 401 | 1. **XOR with salt**: 402 | - 0x12 ^ 0xFE = 0xEC (salt decrements to 0xFE) 403 | - 0x34 ^ 0xFD = 0xC9 (salt decrements to 0xFD) 404 | - 0x56 ^ 0xFC = 0xAA (salt decrements to 0xFC) 405 | - 0xAB ^ 0xFB = 0x50 (salt decrements to 0xFB) 406 | - 0xCD ^ 0xFA = 0x37 (salt decrements to 0xFA) 407 | 408 | 2. **Grouping into 24-bit chunks**: 409 | - First group: 0xECC9AA (0xEC << 16 | 0xC9 << 8 | 0xAA) 410 | - Second group: 0x5037 (incomplete, only 2 bytes) 411 | 412 | 3. **Splitting into 6-bit values**: 413 | - First group: 0x3B, 0x32, 0x2A, 0x2A 414 | - Second group: 0x14, 0x0D, 0x17 415 | 416 | 4. **Character encoding**: 417 | - First group: "zvzz" (using the custom mapping) 418 | - Second group: "MHR" (incomplete - only 3 chars due to truncation) 419 | 420 | 5. **Final output**: "zvzzMHR" 421 | 422 | This example demonstrates the complete encoding process with non-standard padding and salt application. 423 | 424 | ## 5. Decryption Implementation Challenges 425 | 426 | ### 5.1 PRNG Sequence Replication 427 | 428 | The most critical aspect of successful decryption is generating the exact same PRNG byte sequences as used during encryption. Any deviation would cause the XOR operations to produce incorrect results. 429 | 430 | Key technical challenges solved: 431 | 432 | 1. **Identical seeding**: 433 | ```javascript 434 | // Must exactly match encryption seeds 435 | this.prngSeed = 9959949970 ^ customHash(hash) ^ -1748112727; 436 | this.cidPrngSeed = 1809053797 ^ customHash(cid); 437 | ``` 438 | 439 | 2. **State evolution synchronization**: 440 | - Round counters must be initialized to -1 441 | - State mixing must occur after exactly every 3rd call 442 | - Same bit shifting pattern must be followed 443 | 444 | 3. **Salt application matching**: 445 | - Salt must be decremented in the exact same sequence 446 | - Salt XOR must be applied in the same order 447 | 448 | Even a single byte mismatch in the PRNG sequence would cause cascading corruption in the decrypted output due to the XOR operations. 449 | 450 | ### 5.2 Custom Base64 Decoding Implementation 451 | 452 | ```javascript 453 | function decode6Bits(charCode) { 454 | if (charCode >= 97 && charCode <= 122) return charCode - 59; // 'a'-'z' → 38-63 455 | if (charCode >= 65 && charCode <= 90) return charCode - 53; // 'A'-'Z' → 12-37 456 | if (charCode >= 48 && charCode <= 57) return charCode - 46; // '0'-'9' → 2-11 457 | if (charCode === 45) return 0; // '-' → 0 458 | if (charCode === 95) return 1; // '_' → 1 459 | return 0; // fallback 460 | } 461 | ``` 462 | 463 | The decoding process must address these challenges: 464 | 465 | 1. **Reverse character mapping**: 466 | - Each character must be converted back to its correct 6-bit value 467 | - Error handling for invalid characters 468 | 469 | 2. **Handling truncated output**: 470 | - Correctly inferring the original byte count from output length 471 | - Padding incomplete 24-bit chunks appropriately 472 | 473 | 3. **Salt XOR reversal**: 474 | - Applying decremented salt values in the exact same sequence 475 | - Ensuring salt starts at the same value as during encoding 476 | 477 | ### 5.3 JSON Structure Parsing 478 | 479 | The most complex part of decryption is parsing the recovered buffer, which contains a JSON-like structure with no standard format markers: 480 | 481 | ```javascript 482 | function parseBuffer(buffer) { 483 | // Create PRNG for buffer parsing with exact same seed and salt 484 | const prng = createPrng(prngSeed, salt, true); 485 | 486 | // Decrypt the buffer 487 | const decodedBytes = buffer.map(b => b ^ prng()); 488 | 489 | // Convert to string 490 | const jsonStr = String.fromCharCode(...decodedBytes); 491 | 492 | // Parse the JSON-like structure 493 | return parseJsonString(jsonStr); 494 | } 495 | ``` 496 | 497 | Key challenges in this process: 498 | 499 | 1. **Boundary detection**: 500 | - Identifying where key-value pairs begin and end 501 | - Recognizing start markers ('{', ',') and separators (':') 502 | 503 | 2. **Type inference**: 504 | - Determining value types (string, number, boolean, null, array, object) 505 | - Handling nested structures with proper depth tracking 506 | 507 | 3. **Error resilience**: 508 | - Recovering from malformed JSON or corruption 509 | - Handling special characters and escape sequences 510 | 511 | ## 6. Security Analysis 512 | 513 | ### 6.1 Cryptographic Strength Assessment 514 | 515 | DataDome's encryption is not designed as a traditional cryptographic algorithm but as a sophisticated obfuscation technique: 516 | 517 | 1. **Strength metrics**: 518 | - **Bit security**: Approximately 64 bits when both hash and CID are unknown 519 | - **Avalanche effect**: Moderate (changes in hash/CID propagate partially) 520 | - **Confusion/diffusion**: Limited to XOR and PRNG state transitions 521 | 522 | 2. **Security by layers**: 523 | - Layer 1: PRNG with hash seed 524 | - Layer 2: Salt XOR with each byte 525 | - Layer 3: CID-PRNG XOR 526 | - Layer 4: Custom base64-like encoding 527 | 528 | 3. **Attack complexity**: 529 | - Brute force attack: 2^64 operations (infeasible) 530 | - Known-plaintext attack: Feasible with multiple samples 531 | - Implementation attack: Moderate difficulty (requires JS debugging) 532 | 533 | > 🔒 **Need Robust Anti-Bot Protection Solutions?** 534 | > 535 | > Understanding DataDome's vulnerabilities is crucial for both protection and bypass strategies. For professional DataDome bypass solutions built on thorough technical analysis, visit [TakionAPI.tech](https://takionapi.tech) - created by the security researcher who conducted this analysis. 536 | 537 | ### 6.2 Vulnerabilities Analysis 538 | 539 | 1. **Known-plaintext vulnerability**: 540 | With a single known plaintext-ciphertext pair, an attacker could: 541 | - Derive the XOR key stream 542 | - Decrypt other payloads using the same hash/CID 543 | - Potentially infer the PRNG state 544 | 545 | 2. **Deterministic output weakness**: 546 | Same inputs produce same outputs, allowing: 547 | - Fingerprinting of client sessions 548 | - Replay attacks if server doesn't validate timestamps 549 | 550 | 3. **Client-side security limitations**: 551 | - All code and constants are accessible in browser 552 | - PRNG seeds and salt can be extracted through debugging 553 | - JavaScript prototype pollution could potentially compromise the algorithm 554 | 555 | 4. **Cryptographic primitive weaknesses**: 556 | - XOR is reversible with known key 557 | - PRNG is not cryptographically secure 558 | - No authentication or integrity checking 559 | 560 | ### 6.3 Comparison with Standard Encryption Algorithms 561 | 562 | | Feature | DataDome Encryption | AES-256-GCM | Modern WebCrypto | 563 | |------------------------|---------------------|---------------------|---------------------| 564 | | Security level | Medium (obfuscation)| High (cryptographic)| High (cryptographic)| 565 | | Key management | Implicit (hash/CID) | Explicit keys | Managed key hierarchy| 566 | | Implementation complexity| Medium | High | Low (API abstraction)| 567 | | Performance | Fast | Moderate | Varies by platform | 568 | | Resistance to analysis | Medium | High | High | 569 | | Standards compliance | None (proprietary) | FIPS 197, NIST | W3C Web Cryptography | 570 | 571 | ## 7. Performance Analysis and Optimization 572 | 573 | ### 7.1 Computational Complexity 574 | 575 | 1. **Encryption time complexity**: 576 | - O(n) where n is the total size of keys and values 577 | - Dominated by UTF-8 conversion and PRNG operations 578 | 579 | 2. **Memory usage analysis**: 580 | - Peak memory: ~4x the input size due to: 581 | - Original data storage 582 | - UTF-8 encoded buffer 583 | - XORed buffer 584 | - Final encoded output 585 | 586 | 3. **Benchmarks** (on modern browser, i7 processor): 587 | - Small payload (1KB): ~0.5ms encryption, ~0.6ms decryption 588 | - Medium payload (10KB): ~2.5ms encryption, ~3.1ms decryption 589 | - Large payload (100KB): ~23ms encryption, ~29ms decryption 590 | 591 | ### 7.2 Optimization Opportunities 592 | 593 | 1. **PRNG computation optimization**: 594 | - Pre-compute common PRNG sequences 595 | - Batch process state transitions 596 | - Use typed arrays for state management 597 | 598 | 2. **UTF-8 handling improvements**: 599 | - Direct manipulation of bytes instead of character-by-character processing 600 | - Use of TextEncoder/TextDecoder APIs when available 601 | 602 | 3. **Memory efficiency**: 603 | - Streaming processing to avoid full buffer allocation 604 | - In-place XOR operations 605 | - Reuse of buffer objects 606 | 607 | ### 7.3 Mobile Device Considerations 608 | 609 | On resource-constrained devices: 610 | - Processing time increases by ~3x on average mobile devices 611 | - Battery impact is negligible for typical payloads 612 | - Memory usage becomes significant for large payloads (>100KB) 613 | 614 | ## 8. Practical Implementation Considerations 615 | 616 | ### 8.1 Integration Points 617 | 618 | The DataDome encryption is typically used at these integration points: 619 | 620 | 1. **Client fingerprinting data collection**: 621 | - Browser properties 622 | - Canvas fingerprinting 623 | - Device information 624 | 625 | 2. **Challenge response mechanism**: 626 | - CAPTCHA bypass prevention 627 | - Bot detection events 628 | 629 | 3. **API call protection**: 630 | - Request parameter obfuscation 631 | - Authentication token protection 632 | 633 | ### 8.2 Server-Side Processing Requirements 634 | 635 | For server-side decryption, DataDome's system must: 636 | 637 | 1. **Maintain state**: 638 | - Store valid hash/CID pairs 639 | - Track salt values for validation 640 | 641 | 2. **Optimize processing**: 642 | - Parallel decryption of multiple payloads 643 | - Caching of frequent hash/CID combinations 644 | 645 | 3. **Validate content**: 646 | - Check JSON structure integrity 647 | - Verify expected fields are present 648 | - Validate data types and ranges 649 | 650 | ### 8.3 Implementation Tradeoffs 651 | 652 | DataDome's encryption design makes these tradeoffs: 653 | 654 | 1. **Security vs. Performance**: 655 | - Favors speed over cryptographic security 656 | - Operates entirely in JavaScript without WebCrypto 657 | - Avoids expensive operations like SHA-256 or AES 658 | 659 | 2. **Obfuscation vs. Standards**: 660 | - Uses proprietary algorithms instead of standards 661 | - Prioritizes reverse engineering difficulty 662 | - Sacrifices interoperability for uniqueness 663 | 664 | 3. **Complexity vs. Maintenance**: 665 | - Creates complex code that's harder to maintain 666 | - Requires specialized knowledge to update or fix 667 | - Increases risk of bugs or security issues 668 | 669 | ## 9. Conclusion 670 | 671 | DataDome's encryption system represents a pragmatic approach to client-side data protection in the context of anti-bot measures. While not meeting standards for cryptographic security, it provides an effective obfuscation layer that: 672 | 673 | 1. **Deters casual analysis** and prevents immediate data tampering 674 | 2. **Adds computational cost** to automated attacks and scraping attempts 675 | 3. **Creates unique challenges** for reverse engineering efforts 676 | 677 | This analysis demonstrates that even sophisticated obfuscation techniques can be methodically reversed through careful analysis. For legitimate security needs, standard cryptographic algorithms and WebCrypto APIs provide better protection guarantees. 678 | 679 | The successful implementation of a decryption algorithm in this project confirms the complete understanding of DataDome's encryption system and highlights both its strengths and limitations as a security measure in the anti-bot protection landscape. 680 | 681 | ## 10. Industry Context and Modern Security Practices 682 | 683 | ### 10.1 Comparison with Industry Competitors 684 | 685 | DataDome's encryption approach differs significantly from other leading anti-bot providers: 686 | 687 | | Provider | Encryption Approach | Key Management | Evolution Strategy | 688 | |---------------------|--------------------------------------------------|------------------------------------- |------------------------------------------| 689 | | DataDome | Custom obfuscation algorithm | Static, derived from hash and CID | Minimal changes over multiple years | 690 | | Akamai Bot Manager | Standard TLS + proprietary token encryption | Dynamic, session-based key rotation | Regular algorithm updates and rotation | 691 | | PerimeterX/HUMAN | Standard cryptography + JS VM attestation | Server-generated, short-lived keys | Continuous security improvement | 692 | | Cloudflare | Layered TLS + challenge-specific encryption | Ephemeral keys with rapid rotation | Adaptive security posture | 693 | | Shape Security | Polymorphic code + cryptographic attestation | Multi-layered key hierarchy | Constantly evolving protection | 694 | 695 | The most concerning aspect of DataDome's approach is the static nature of their algorithm. While competitors continuously evolve their protection mechanisms, DataDome has maintained essentially the same encryption scheme since its initial deployment. 696 | 697 | ### 10.2 Modern Web Security Best Practices 698 | 699 | Current web security standards that DataDome could implement include: 700 | 701 | 1. **Web Cryptography API Integration** 702 | 703 | Modern browsers provide the Web Cryptography API, which offers standardized implementations of cryptographic primitives: 704 | 705 | ```javascript 706 | // Example of modern approach using WebCrypto 707 | async function encryptData(data, key) { 708 | const encodedData = new TextEncoder().encode(JSON.stringify(data)); 709 | const iv = crypto.getRandomValues(new Uint8Array(12)); 710 | 711 | const encryptedData = await window.crypto.subtle.encrypt( 712 | { name: "AES-GCM", iv: iv }, 713 | key, 714 | encodedData 715 | ); 716 | 717 | return { 718 | iv: Array.from(iv).map(b => b.toString(16).padStart(2, '0')).join(''), 719 | data: Array.from(new Uint8Array(encryptedData)) 720 | .map(b => b.toString(16).padStart(2, '0')).join('') 721 | }; 722 | } 723 | ``` 724 | 725 | 2. **Security Through Key Management** 726 | 727 | Rather than relying on obfuscation, modern security emphasizes proper key management: 728 | 729 | - Short-lived session keys 730 | - Server-side key generation and validation 731 | - Key rotation mechanisms 732 | - Compartmentalized key usage (different keys for different purposes) 733 | 734 | 3. **Defense in Depth** 735 | 736 | Modern anti-bot systems use multiple layers of protection: 737 | 738 | - TLS for transport security 739 | - Standard encryption for sensitive data 740 | - Integrity verification through cryptographic signatures 741 | - Challenge diversity rather than a single protection mechanism 742 | - Server-side verification of client integrity 743 | 744 | ### 10.3 Technical Debt in Security Systems 745 | 746 | DataDome's approach appears to carry significant technical debt in its security implementation: 747 | 748 | 1. **Legacy Design Patterns** 749 | - Reliance on custom cryptography over standards 750 | - Browser compatibility compromises that may no longer be necessary 751 | - Monolithic algorithm rather than modular security components 752 | 753 | 2. **Maintenance Challenges** 754 | - Highly specialized code that's difficult to maintain 755 | - Tightly coupled implementation resistant to incremental improvements 756 | - Limited ability to respond to new threats or vulnerabilities 757 | 758 | 3. **Scaling Limitations** 759 | - Static security posture that doesn't adapt to emerging threats 760 | - Fixed protection mechanism that attackers can study over time 761 | - Lack of contextual security that could provide better protection with lower false positives 762 | 763 | ### 10.4 Business Implications 764 | 765 | For a $33M anti-bot security company like DataDome, the technical approach observed in their encryption implementation raises important business considerations: 766 | 767 | 1. **Risk Exposure** 768 | - Once the algorithm is reverse-engineered, all clients using the same implementation are potentially affected 769 | - The static nature of the implementation means that exploits have a long effective lifetime 770 | - Lack of cryptographic guarantees may impact compliance with security standards 771 | 772 | 2. **Competitive Disadvantage** 773 | - More advanced competitors are implementing adaptive, standards-based security 774 | - Technical debt can impede the ability to rapidly respond to new threats 775 | - Customers seeking cutting-edge protection may choose more progressive solutions 776 | 777 | 3. **Opportunity Cost** 778 | - Resources spent maintaining custom cryptography could be redirected to more effective security measures 779 | - Embracing modern standards could free up engineering resources for innovation 780 | - Decoupled security components would allow more rapid iteration and improvement 781 | 782 | ### 10.5 Recommendations for Improvement 783 | 784 | Based on this analysis, several recommendations could significantly improve DataDome's client-side security implementation: 785 | 786 | 1. **Near-term Improvements** 787 | - Implement standard cryptographic algorithms via WebCrypto API 788 | - Add message authentication codes (MAC) for data integrity verification 789 | - Introduce server-side validation of encryption integrity 790 | - Implement proper key rotation mechanisms 791 | 792 | 2. **Medium-term Strategy** 793 | - Develop a polymorphic challenge generation system 794 | - Move from obfuscation to standards-based security with proper key management 795 | - Create a modular security framework allowing component-level updates 796 | - Implement a proper key hierarchy with different levels of security for different data 797 | 798 | 3. **Long-term Vision** 799 | - Develop an adaptive security posture that evolves based on threat intelligence 800 | - Implement contextual security that adjusts protection based on risk factors 801 | - Build a security ecosystem rather than a single protection mechanism 802 | - Invest in cutting-edge approaches like secure multi-party computation or zero-knowledge proofs for sensitive operations 803 | 804 | This analysis underscores the importance of continuous security evolution and the risks of relying on static security-through-obscurity approaches in the rapidly evolving landscape of web security and bot protection. 805 | 806 | ## 11. Challenge Type Differences 807 | 808 | ### 11.1 Technical Comparison between CAPTCHA and Interstitial 809 | 810 | DataDome implements two distinct challenge types, each with its own cryptographic parameters: 811 | 812 | 1. **CAPTCHA Challenge** (default) 813 | - Dynamic HSV generation based on the hash and random values 814 | - Hash XOR constant: -1748112727 815 | - Applies standard base64 padding handling 816 | - Typically used for standard CAPTCHA protection flows 817 | 818 | 2. **Interstitial Challenge** 819 | - Fixed HSV value: "9E9FC74889F6" 820 | - Different Hash XOR constant: -883841716 821 | - Does not apply padding handling in base64 decoding 822 | - Used for interstitial pages that appear between navigation 823 | 824 | ### 11.2 Implementation Differences 825 | 826 | Both challenge types share the core encryption algorithm but differ in initialization and handling: 827 | 828 | ```javascript 829 | // CAPTCHA challenge initialization 830 | const captchaEncryptor = new DataDomeEncryptor( 831 | "14D062F60A4BDE8CE8647DFC720349", // Typical CAPTCHA hash 832 | clientId, 833 | null, 834 | "captcha" // Default value 835 | ); 836 | 837 | // Interstitial challenge initialization 838 | const interstitialEncryptor = new DataDomeEncryptor( 839 | "14D062F60A4BDE8CE8647DFC720349", // Same hash can be used 840 | clientId, 841 | null, 842 | "interstitial" 843 | ); 844 | ``` 845 | 846 | **Key functional differences:** 847 | 848 | 1. **Base64 Decoding**: 849 | ```javascript 850 | // In decryption.js: 851 | if (this.challengeType === 'interstitial') { 852 | return bytes; // Skip padding handling for interstitial 853 | } 854 | 855 | // Handle padding if needed (only for CAPTCHA challenges) 856 | let mod = encoded.length % 4; 857 | if (mod) { 858 | bytes = bytes.slice(0, bytes.length - (3 - mod)); 859 | } 860 | ``` 861 | 862 | 2. **HSV Value**: 863 | ```javascript 864 | // In encryption.js: 865 | if (this.challengeType === 'interstitial') { 866 | this._hsv = "9E9FC74889F6"; // Fixed value for interstitial 867 | } else { 868 | this._hsv = this._generateHsv(); // Dynamic for CAPTCHA 869 | } 870 | ``` 871 | 872 | ### 11.3 Security Implications of Multiple Challenge Types 873 | 874 | The existence of multiple challenge types with different constants has several implications: 875 | 876 | 1. **Increases implementation complexity** 877 | - Requires maintaining multiple sets of constants 878 | - Creates opportunity for misconfiguration 879 | - Requires careful handling of padding differences 880 | 881 | 2. **Provides limited security segmentation** 882 | - Different challenge types can't be decoded with each other's parameters 883 | - Acts as a form of namespace separation 884 | - Helps isolate potential vulnerabilities between challenge types 885 | 886 | 3. **Offers minimal additional protection** 887 | - Once the algorithm is understood, both challenge types are equally vulnerable 888 | - Same core PRNG and encoding algorithms are used 889 | - The primary difference is in constants rather than algorithm structure 890 | 891 | 4. **Compatibility considerations** 892 | - The padding handling difference suggests the interstitial challenges may have been implemented to work around compatibility issues in some environments 893 | - The simpler padding approach for interstitial challenges potentially increases compatibility at a minor cost to output format standardization 894 | 895 | --- 896 | 897 | ## Author 898 | 899 | If you found this project helpful or interesting, consider starring the repo and following me for more security research and tools, or buy me a coffee to keep me up. 900 | 901 | Stay in touch with me via discord or mail or anything. 902 | 903 |

904 | GitHub 905 | Twitter 906 | Medium 907 | Discord 908 | Email 909 | Buy Me a Coffee 910 |

911 | 912 | --------------------------------------------------------------------------------