├── .gitignore ├── Eval加密解密原理与工具.pdf ├── LICENSE ├── README.assets ├── 784924-20180225023303642-218023791.gif ├── image-20231030132026541-7614065.png ├── image-20241016230653669.png ├── image-20241016231143315.png └── image-20241017220641930.png ├── README.md ├── README_en.md ├── css └── styles.css ├── eval-decoder.js ├── eval-example.js ├── examples └── eval-example.js ├── index.html └── js ├── eval-decoder.js └── main.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /Eval加密解密原理与工具.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JSREI/eval-decoder/f5d7948d9765de87cbcd1f4fe8e5a19127c63189/Eval加密解密原理与工具.pdf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 JavaScript Reverse Engineering Infrastructure 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.assets/784924-20180225023303642-218023791.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JSREI/eval-decoder/f5d7948d9765de87cbcd1f4fe8e5a19127c63189/README.assets/784924-20180225023303642-218023791.gif -------------------------------------------------------------------------------- /README.assets/image-20231030132026541-7614065.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JSREI/eval-decoder/f5d7948d9765de87cbcd1f4fe8e5a19127c63189/README.assets/image-20231030132026541-7614065.png -------------------------------------------------------------------------------- /README.assets/image-20241016230653669.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JSREI/eval-decoder/f5d7948d9765de87cbcd1f4fe8e5a19127c63189/README.assets/image-20241016230653669.png -------------------------------------------------------------------------------- /README.assets/image-20241016231143315.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JSREI/eval-decoder/f5d7948d9765de87cbcd1f4fe8e5a19127c63189/README.assets/image-20241016231143315.png -------------------------------------------------------------------------------- /README.assets/image-20241017220641930.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JSREI/eval-decoder/f5d7948d9765de87cbcd1f4fe8e5a19127c63189/README.assets/image-20241017220641930.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Eval加密解密原理与工具 2 | 3 | GitHub Repository:https://github.com/JSREI/eval-decoder 4 | 5 | 简体中文| [English](README_en.md) 6 | 7 | # 一、在线解密 8 | 9 | 点击链接进入在线解密页面: 10 | 11 | [https://jsrei.github.io/eval-decoder/](https://jsrei.github.io/eval-decoder/) 12 | 13 | ![image-20241017220641930](./README.assets/image-20241017220641930.png) 14 | 15 | # 二、部署说明 16 | 17 | ## 2.1 本地部署 18 | 19 | 克隆仓库后,直接在浏览器中打开 `index.html` 文件即可使用。 20 | 21 | ## 2.2 GitHub Pages部署 22 | 23 | 1. Fork本仓库到你的GitHub账号 24 | 2. 启用GitHub Pages: 25 | - 进入仓库设置 (Settings) 26 | - 找到 "Pages" 选项 27 | - 在 "Source" 部分选择 "main" 分支 28 | - 保存设置 29 | 3. 几分钟后,你的工具将可以通过 `https://<你的用户名>.github.io/eval-decoder/` 访问 30 | 31 | # 三、原理探究:jspacker压缩及解压缩研究(js eval) 32 | 33 | ## 2.1 起因 34 | 35 | 在研究爬虫的时候发现很多网站都出现了同一种方式的js混淆,并且名字都是pde.js,怀疑是使用了同一款混淆工具,所以研究一下。 36 | 37 | 这款工具叫JS Packer,并不是专门的混淆工具,而是一款js压缩工具,其官网地址为: http://dean.edwards.name/packer/ 38 | 39 | 支持两种压缩方式,一种是Shrink variables比较常规的压缩方式,就是去掉一些空白符注释之类的,另一种是Base62 encode,是一种比较适合用来压缩内容单词重复率高的压缩方式。 40 | 41 | ## 2.2 压缩示例 42 | 43 | 所有讨论基于Base62 encode压缩方式,输入: 44 | 45 | ``` js 46 | alter("hello, world"); 47 | ``` 48 | 49 | 输出: 50 | 51 | ```js 52 | eval(function(p,a,c,k,e,r){e=String;if(!''.replace(/^/,String)){while(c--)r[c]=k[c]||c;k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('0("1, 2");',3,3,'alter|hello|world'.split('|'),0,{})) 53 | ``` 54 | 55 | 格式化后: 56 | 57 | ```js 58 | eval(function (p, a, c, k, e, r) { 59 | e = String; 60 | if (!''.replace(/^/, String)) { 61 | while (c--) r[c] = k[c] || c; 62 | k = [function (e) { 63 | return r[e] 64 | }]; 65 | e = function () { 66 | return '\\w+' 67 | }; 68 | c = 1 69 | } 70 | ; 71 | while (c--) if (k[c]) p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]); 72 | return p 73 | }('0("1, 2");', 3, 3, 'alter|hello|world'.split('|'), 0, {})) 74 | ``` 75 | 76 | 面的代码看着很唬人,其实原理很简单,我们耐心分析下。 77 | 78 | ## 2.3 压缩原理: 79 | 80 | 简单来说就是将相同的单词进行压缩,具体为将所有单词抽取出来作为一个词典,然后将源代码中表示单词的地方改为引用词典的下标,这样的话当重复的单词很多的时候压缩效果就比较好,但是当重复的单词比较少的时候这种方法有点得不偿失。 81 | 82 | 带入具体数据来具体分析,比如下面的代码: 83 | 84 | ```js 85 | console.log("aaaaa"); 86 | console.log("aaaaa"); 87 | console.log("bbbb"); 88 | ``` 89 | 90 | 压缩之后格式化: 91 | 92 | ```js 93 | eval(function(p, a, c, k, e, r) { 94 | e = String; 95 | if (!''.replace(/^/, String)) { 96 | while (c--) r[c] = k[c] || c; 97 | k = [function(e) { 98 | return r[e] 99 | }]; 100 | e = function() { 101 | return '\\w+' 102 | }; 103 | c = 1 104 | }; 105 | while (c--) if (k[c]) p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]); 106 | return p 107 | } ('0.1("2");0.1("2");0.1("3");', 4, 4, 'console|log|aaaaa|bbbb'.split('|'), 0, {})) 108 | ``` 109 | 110 | 可以看到规律已经很明显了,第一个 参数 '0.1("2");0.1("2");0.1("3");'中的数字对应着 'console|log|aaaaa|bbbb'.split('|') 中的下标,解压的时候只需要再将数字下标还原为单词即可。 111 | 112 | 下面是对解压缩算法的一个简单解读: 113 | 114 | ```js 115 | // p 将原始内容中所有单词替换为字典下标后的压缩内容 116 | // a 词典大小,暂时用不到 117 | // c 词典大小,在解压时用来关联压缩内容和词典 118 | // k 词典 119 | // e 在解压时,当replace第二个参数支持function时,为\\w+,否则为与下标对应的字符串 120 | // r 当加速解压时用来保存词典 121 | eval(function(p, a, c, k, e, r) { 122 | e = String; 123 | 124 | // 检测当前的浏览器是否支持replace(regex, function),如果支持的话就能够加快解压速度 125 | // 如果不支持的话可以把这一块直接忽略掉 126 | if (!''.replace(/^/, String)) { 127 | 128 | // 把被压缩的单词拷贝一份,因为k还有别的用处 129 | while (c--) r[c] = k[c] || c; 130 | 131 | // k[0]后面用来对每个匹配到的下标寻找替换字符串 132 | k = [function(e) { 133 | return r[e] 134 | }]; 135 | 136 | // 用来分割原始内容 137 | e = function() { 138 | return '\\w+' 139 | }; 140 | 141 | // 加速解压的时候,相当于把while变成了if 142 | c = 1 143 | }; 144 | 145 | // 使用词典将压缩后的下标代码扩展,如果没有上面的加速的话,c等于词典单词数,要一个一个替换了 146 | // 如果支持replace(string, function)的话,会将匹配到的每一个数字都传递给k[c]来得到其应该被替换为的字符串 147 | while (c--) if (k[c]) p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]); 148 | 149 | // 完成解压 150 | return p 151 | } ('0.1("2");0.1("2");0.1("3");', 4, 4, 'console|log|aaaaa|bbbb'.split('|'), 0, {})) 152 | ``` 153 | 154 | ## 2.4 解压缩小工具 155 | 156 | 我把这种 eval(blablabla…) 形式的统称为eval压缩,并针对此写了个一个简单的解压小工具。 157 | 158 | 思路: 159 | 160 | 1. 既然这种肯定是要在网页上执行的,那么只需要模拟执行就可以了。 161 | 162 | 2. 可能不只有一层eval,所以应该能够方便的多次连续eval。 163 | 164 | html代码如下: 165 | 166 | ```html 167 | 168 | 169 | 170 | JavaScript Eval解密 171 | 172 | 173 | 174 | 176 | 177 | 178 | 205 | 206 | 207 | ``` 208 | 209 | 效果如下: 210 | 211 | ![1](README.assets/784924-20180225023303642-218023791.gif) 212 | 213 | # 三、参考资料 214 | 215 | - [https://www.cnblogs.com/cc11001100/p/8468508.html](https://www.cnblogs.com/cc11001100/p/8468508.html) 216 | 217 | # 四、逆向技术交流群 218 | 219 | 扫码加入逆向技术交流群: 220 | 221 | 222 | 223 | 如群二维码过期,可以加我个人微信,发送【逆向群】拉你进群: 224 | 225 | 226 | 227 | [点此](https://t.me/jsreijsrei)或扫码加入TG交流群: 228 | 229 | 230 | 231 | 232 | 233 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | # Eval Encryption and Decryption Principles and Tools 2 | 3 | GitHub Repository: [https://github.com/JSREI/eval-decoder](https://github.com/JSREI/eval-decoder) 4 | 5 | [简体中文](./README.md) | English 6 | 7 | ## 1. Online Decryption 8 | 9 | Click the link to enter the online decryption page: 10 | 11 | [https://jsrei.github.io/eval-decoder/](https://jsrei.github.io/eval-decoder/) 12 | 13 | ![image-20241017220641930](./README.assets/image-20241017220641930.png) 14 | 15 | ## 2. Deployment Instructions 16 | 17 | ### 2.1 Local Deployment 18 | 19 | After cloning the repository, simply open the `index.html` file in your browser to use the tool. 20 | 21 | ### 2.2 GitHub Pages Deployment 22 | 23 | 1. Fork this repository to your GitHub account 24 | 2. Enable GitHub Pages: 25 | - Go to repository settings (Settings) 26 | - Find the "Pages" option 27 | - Choose "main" branch in the "Source" section 28 | - Save the settings 29 | 3. After a few minutes, your tool will be accessible via `https://.github.io/eval-decoder/` 30 | 31 | ## 3. Principle Exploration: JSPacker Compression and Decompression Study (js eval) 32 | 33 | ### 3.1 Cause 34 | 35 | While studying web crawlers, I found that many websites had the same type of JS obfuscation, and the names were all pde.js. I suspected that the same obfuscation tool was used, so I decided to research it. 36 | 37 | This tool is called JS Packer, which is not a dedicated obfuscation tool but a JS compression tool. Its official website address is: [http://dean.edwards.name/packer/](http://dean.edwards.name/packer/) 38 | 39 | It supports two compression methods. One is the conventional Shrinking variables, which removes whitespace and comments, etc. The other is Base62 encoding, which is suitable for compressing content with a high repetition rate of words. 40 | 41 | ### 3.2 Compression Example 42 | 43 | All discussions are based on the Base62 encode compression method. Input: 44 | 45 | ``` js 46 | alter("hello, world"); 47 | ``` 48 | 49 | Output: 50 | 51 | ```js 52 | eval(function(p,a,c,k,e,r){e=String;if(!''.replace(/^/,String)){while(c--)r[c]=k[c]||c;k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('0("1, 2");',3,3,'alter|hello|world'.split('|'),0,{})) 53 | ``` 54 | 55 | Formatted: 56 | 57 | ```js 58 | eval(function (p, a, c, k, e, r) { 59 | e = String; 60 | if (!''.replace(/^/, String)) { 61 | while (c--) r[c] = k[c] || c; 62 | k = [function (e) { 63 | return r[e] 64 | }]; 65 | e = function () { 66 | return '\\w+' 67 | }; 68 | c = 1 69 | } 70 | ; 71 | while (c--) if (k[c]) p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]); 72 | return p 73 | }('0("1, 2");', 3, 3, 'alter|hello|world'.split('|'), 0, {})) 74 | ``` 75 | 76 | The code above may look intimidating, but the principle is quite simple. Let's analyze it patiently. 77 | 78 | ### 3.3 Compression Principle: 79 | 80 | In simple terms, it compresses the same words by extracting all words as a dictionary and then changing the places in the source code that represent words to reference the index of the dictionary. This method works well when there are many repeated words, but it may not be worth it when there are few repeated words. 81 | 82 | Let's analyze with specific data, such as the following code: 83 | 84 | ```js 85 | console.log("aaaaa"); 86 | console.log("aaaaa"); 87 | console.log("bbbb"); 88 | ``` 89 | 90 | Formatted after compression: 91 | 92 | ```js 93 | eval(function(p, a, c, k, e, r) { 94 | e = String; 95 | if (!''.replace(/^/, String)) { 96 | while (c--) r[c] = k[c] || c; 97 | k = [function(e) { 98 | return r[e] 99 | }]; 100 | e = function() { 101 | return '\\w+' 102 | }; 103 | c = 1 104 | }; 105 | while (c--) if (k[c]) p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]); 106 | return p 107 | } ('0.1("2");0.1("2");0.1("3");', 4, 4, 'console|log|aaaaa|bbbb'.split('|'), 0, {})) 108 | ``` 109 | 110 | The pattern is already very clear. The numbers in the first parameter '0.1("2");0.1("2");0.1("3");' correspond to the indices in 'console|log|aaaaa|bbbb'.split('|'). When decompressing, you just need to restore the numerical indices to words. 111 | 112 | Here is a simple interpretation of the decompression algorithm: 113 | 114 | ```js 115 | // p The compressed content where all words in the original content are replaced with dictionary indices 116 | // a Dictionary size, not used for now 117 | // c Dictionary size, used to associate compressed content with the dictionary during decompression 118 | // k Dictionary 119 | // e During decompression, if the second argument of replace supports function, it is \\w+, otherwise it is the string corresponding to the index 120 | // r Used to save the dictionary when speeding up decompression 121 | eval(function(p, a, c, k, e, r) { 122 | e = String; 123 | 124 | // Detect whether the current browser supports replace(regex, function), which can speed up decompression if supported 125 | // If not supported, this part can be ignored directly 126 | if (!''.replace(/^/, String)) { 127 | 128 | // Copy a compressed word because k has other uses 129 | while (c--) r[c] = k[c] || c; 130 | 131 | // k[0] is later used to find the replacement string for each matched index 132 | k = [function(e) { 133 | return r[e] 134 | }]; 135 | 136 | // Used to split the original content 137 | e = function() { 138 | return '\\w+' 139 | }; 140 | 141 | // When accelerating decompression, it is equivalent to changing while to if 142 | c = 1 143 | }; 144 | 145 | // Use the dictionary to expand the compressed index code. If there is no acceleration above, c equals the number of dictionary words, and each one needs to be replaced one by one 146 | // If replace(string, function) is supported, it will pass each matched number to k[c] to get the string it should be replaced with 147 | while (c--) if (k[c]) p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]); 148 | 149 | // Complete decompression 150 | return p 151 | } ('0.1("2");0.1("2");0.1("3");', 4, 4, 'console|log|aaaaa|bbbb'.split('|'), 0, {})) 152 | ``` 153 | 154 | ### 3.4 Decompression Tool 155 | 156 | I call this form of eval(blablabla…) eval compression and have written a simple decompression tool for it. 157 | 158 | Thoughts: 159 | 160 | 1. Since this is definitely going to be executed on the web page, it can be simulated by just executing it. 161 | 162 | 2. There may be more than one layer of eval, so it should be easy to perform multiple consecutive evals. 163 | 164 | The HTML code is as follows: 165 | 166 | ```html 167 | 168 | 169 | 170 | JavaScript Eval Decryption 171 | 172 | 173 | 174 | 176 | 177 | 178 | 206 | 207 | 208 | ``` 209 | 210 | The effect is as follows: 211 | 212 | ![1](README.assets/784924-20180225023303642-218023791.gif) 213 | 214 | ## 4. Reference Materials 215 | 216 | - [https://www.cnblogs.com/cc11001100/p/8468508.html](https://www.cnblogs.com/cc11001100/p/8468508.html) 217 | 218 | ## 5. Reverse Engineering Technical Exchange Group 219 | 220 | Scan the code to join the reverse engineering technical exchange group: 221 | 222 | 223 | 224 | If the group QR code has expired, you can add my personal WeChat and send [Reverse Group] to pull you into the group: 225 | 226 | 227 | 228 | [Click here](https://t.me/jsreijsrei) or scan the code to join the TG exchange group: 229 | 230 | 231 | -------------------------------------------------------------------------------- /css/styles.css: -------------------------------------------------------------------------------- 1 | /* 整个页面使用Flexbox布局 */ 2 | body { 3 | display: flex; 4 | justify-content: center; /* 水平居中 */ 5 | align-items: center; /* 垂直居中 */ 6 | min-height: 100vh; /* 最小高度为视口高度 */ 7 | margin: 0; 8 | background-color: #f0f0f0; /* 背景颜色 */ 9 | font-family: Arial, sans-serif; /* 字体 */ 10 | } 11 | 12 | .container { 13 | display: flex; 14 | flex-direction: column; /* 垂直排列子元素 */ 15 | align-items: center; /* 子元素水平居中 */ 16 | max-width: 800px; 17 | padding: 20px; 18 | width: 100%; 19 | } 20 | 21 | .button-container { 22 | display: flex; 23 | gap: 15px; 24 | flex-wrap: wrap; 25 | justify-content: center; 26 | margin-bottom: 20px; 27 | } 28 | 29 | .cool-button { 30 | padding: 10px 20px; 31 | font-size: 16px; 32 | text-transform: uppercase; 33 | color: #fff; 34 | background: linear-gradient(45deg, #4b6cb7, #182848); 35 | border: none; 36 | border-radius: 5px; 37 | cursor: pointer; 38 | transition: all 0.3s ease; 39 | outline: none; 40 | position: relative; 41 | overflow: hidden; 42 | margin-bottom: 20px; /* 添加底部间距 */ 43 | } 44 | 45 | .cool-button::before { 46 | content: ''; 47 | position: absolute; 48 | top: 0; 49 | left: -50%; 50 | width: 100%; 51 | height: 100%; 52 | background: rgba(255, 255, 255, 0.1); 53 | transform: skewX(-20deg); 54 | transition: all 0.3s ease; 55 | } 56 | 57 | .cool-button:hover::before { 58 | transform: skewX(20deg) translateX(150%); 59 | } 60 | 61 | .cool-button:hover { 62 | background: linear-gradient(45deg, #182848, #4b6cb7); 63 | } 64 | 65 | .cool-textarea { 66 | width: 100%; 67 | max-width: 700px; 68 | padding: 15px; 69 | font-size: 16px; 70 | color: #333; 71 | background: #f7f7f7; 72 | border: 2px solid #4b6cb7; 73 | border-radius: 5px; 74 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1); 75 | transition: all 0.3s ease; 76 | resize: vertical; /* 允许垂直调整大小 */ 77 | min-height: 300px; 78 | } 79 | 80 | .cool-textarea:focus { 81 | border-color: #182848; 82 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 5px rgba(25, 25, 25, 0.4); 83 | outline: none; 84 | } 85 | 86 | .cool-textarea::placeholder { 87 | color: #a9a9a9; 88 | } 89 | 90 | h1 { 91 | font-size: 2.5em; /* 字体大小 */ 92 | color: #4b6cb7; /* 字体颜色,与按钮颜色相匹配 */ 93 | text-align: center; /* 文本居中 */ 94 | margin: 0; /* 移除默认的外边距 */ 95 | padding: 20px 0; /* 上下内边距 */ 96 | font-weight: normal; /* 字体权重 */ 97 | background: linear-gradient(to right, #4b6cb7, #182848); /* 背景渐变 */ 98 | -webkit-background-clip: text; /* 背景剪切,文本颜色变为透明 */ 99 | background-clip: text; 100 | color: transparent; /* 文本颜色变为透明 */ 101 | transition: all 0.3s ease; /* 平滑过渡效果 */ 102 | } 103 | 104 | h1:hover { 105 | transform: scale(1.05); /* 鼠标悬停时放大 */ 106 | } 107 | 108 | .language-switch { 109 | margin-top: 10px; 110 | display: flex; 111 | gap: 10px; 112 | } 113 | 114 | .language-switch a { 115 | color: #4b6cb7; 116 | text-decoration: none; 117 | font-weight: bold; 118 | padding: 3px 8px; 119 | border-radius: 4px; 120 | transition: all 0.2s ease; 121 | } 122 | 123 | .language-switch a:hover { 124 | text-decoration: none; 125 | background-color: rgba(75, 108, 183, 0.1); 126 | } 127 | 128 | .language-switch .separator { 129 | color: #4b6cb7; 130 | margin: 0 5px; 131 | } 132 | 133 | #add-favorite-btn { 134 | display: inline-flex; 135 | align-items: center; 136 | background-color: rgba(75, 108, 183, 0.05); 137 | } 138 | 139 | #add-favorite-btn:hover { 140 | background-color: rgba(75, 108, 183, 0.2); 141 | } 142 | 143 | .reference-section { 144 | margin-top: 20px; 145 | text-align: left; 146 | width: 100%; 147 | max-width: 700px; 148 | } 149 | 150 | .reference-section ul { 151 | padding-left: 20px; 152 | } 153 | 154 | .reference-section a { 155 | color: #4b6cb7; 156 | text-decoration: none; 157 | transition: color 0.2s ease; 158 | } 159 | 160 | .reference-section a:hover { 161 | color: #182848; 162 | text-decoration: underline; 163 | } 164 | 165 | .group-section { 166 | margin-top: 30px; 167 | text-align: center; 168 | border-top: 1px solid #ddd; 169 | padding-top: 20px; 170 | width: 100%; 171 | } 172 | 173 | .group-section h2 { 174 | color: #4b6cb7; 175 | margin-bottom: 20px; 176 | font-size: 1.8em; 177 | } 178 | 179 | .group-section p { 180 | margin-bottom: 15px; 181 | } 182 | 183 | .group-section img { 184 | border: 1px solid #ddd; 185 | border-radius: 5px; 186 | box-shadow: 0 2px 5px rgba(0,0,0,0.1); 187 | margin: 10px 0; 188 | transition: transform 0.3s ease, box-shadow 0.3s ease; 189 | } 190 | 191 | .group-section img:hover { 192 | transform: translateY(-5px); 193 | box-shadow: 0 5px 15px rgba(0,0,0,0.15); 194 | } 195 | 196 | .group-container { 197 | display: flex; 198 | flex-wrap: wrap; 199 | justify-content: center; 200 | gap: 20px; 201 | } 202 | 203 | .group-item { 204 | flex: 1; 205 | min-width: 200px; 206 | max-width: 250px; 207 | margin-bottom: 20px; 208 | } 209 | 210 | /* 响应式设计 */ 211 | @media (max-width: 768px) { 212 | .container { 213 | padding: 10px; 214 | } 215 | 216 | h1 { 217 | font-size: 1.8em; 218 | } 219 | 220 | .cool-textarea { 221 | min-height: 200px; 222 | } 223 | 224 | .cool-button { 225 | padding: 8px 16px; 226 | font-size: 14px; 227 | margin-bottom: 10px; 228 | } 229 | 230 | .button-container { 231 | gap: 10px; 232 | } 233 | 234 | .group-item { 235 | min-width: 150px; 236 | max-width: 100%; 237 | } 238 | 239 | .group-section img { 240 | width: 150px; 241 | } 242 | 243 | .language-switch { 244 | margin-bottom: 10px; 245 | } 246 | 247 | .site-footer { 248 | margin-top: 30px; 249 | padding-top: 15px; 250 | font-size: 0.8em; 251 | } 252 | } 253 | 254 | /* 页脚样式 */ 255 | .site-footer { 256 | margin-top: 40px; 257 | padding-top: 20px; 258 | border-top: 1px solid #ddd; 259 | width: 100%; 260 | text-align: center; 261 | font-size: 0.9em; 262 | color: #666; 263 | } 264 | 265 | .site-footer a { 266 | color: #4b6cb7; 267 | text-decoration: none; 268 | transition: color 0.2s ease; 269 | } 270 | 271 | .site-footer a:hover { 272 | color: #182848; 273 | text-decoration: underline; 274 | } 275 | 276 | /* 确保列表项符号始终显示 */ 277 | .reference-section ul li { 278 | display: list-item !important; 279 | } 280 | 281 | .reference-section ul li.lang-zh, 282 | .reference-section ul li.lang-en { 283 | display: none; 284 | } 285 | 286 | .reference-section ul li.lang-zh[style*="display: block"], 287 | .reference-section ul li.lang-en[style*="display: block"] { 288 | display: list-item !important; 289 | } -------------------------------------------------------------------------------- /eval-decoder.js: -------------------------------------------------------------------------------- 1 | 2 | const evalHolder = window.eval; 3 | window.eval = function (jsCode) { 4 | return jsCode; 5 | } 6 | 7 | /** 8 | * 对eval加密过的JS代码进行解密 9 | * @param evalJsCode {String} 10 | */ 11 | function evalDecode(evalJsCode) { 12 | return evalHolder.apply(this, evalJsCode) 13 | } 14 | 15 | -------------------------------------------------------------------------------- /eval-example.js: -------------------------------------------------------------------------------- 1 | eval(function(p,a,c,k,e,r){e=String;if(!''.replace(/^/,String)){while(c--)r[c]=k[c]||c;k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('0.1("2");',3,3,'console|log|CC11001100'.split('|'),0,{})) 2 | -------------------------------------------------------------------------------- /examples/eval-example.js: -------------------------------------------------------------------------------- 1 | /** 2 | * eval-example.js 3 | * 这是一个eval加密的示例代码 4 | * 5 | * 解密后将输出:console.log("CC11001100"); 6 | */ 7 | eval(function(p,a,c,k,e,r){e=String;if(!''.replace(/^/,String)){while(c--)r[c]=k[c]||c;k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('0.1("2");',3,3,'console|log|CC11001100'.split('|'),0,{})) -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JavaScript eval解密工具 - JSREI 7 | 8 | 9 | 10 | 11 | 12 | 19 | 20 | 21 | 22 |
23 |

解密eval加密的代码

24 | 25 |
26 | 中文 | 27 | English 28 |
29 | 30 | 31 | 32 |
33 |
34 | 35 |
36 | 37 | 38 | 39 |
40 | 41 |
42 |
43 | 44 |
45 |

参考链接:

46 | 47 | 53 |
54 | 55 | 56 |
57 |

逆向技术交流群

58 | 59 | 60 |
61 |
62 |

扫码加入逆向技术交流群:

63 | 64 | 加入微信群 65 |
66 | 67 |
68 |

如群二维码过期,可以加我个人微信,发送【逆向群】拉你进群:

69 | 70 | 个人微信 71 |
72 | 73 |
74 |

点此或扫码加入TG交流群:

75 | 76 | Telegram群 77 |
78 |
79 |
80 | 81 | 82 |
83 | 84 |
85 | 86 | 87 | 89 | Star me on GitHub 90 | 91 |
92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /js/eval-decoder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * eval-decoder.js 3 | * 对eval加密过的JS代码进行解密的工具函数 4 | * 5 | * GitHub: https://github.com/JSREI/eval-decoder 6 | */ 7 | 8 | // 保存原始的eval函数 9 | const evalHolder = window.eval; 10 | 11 | // 重写eval函数以返回代码而不是执行它 12 | window.eval = function (jsCode) { 13 | return jsCode; 14 | }; 15 | 16 | /** 17 | * 获取当前语言设置 18 | * @returns {string} 当前语言代码 'zh' 或 'en' 19 | */ 20 | function getCurrentLanguage() { 21 | // 从window对象获取currentLang(在main.js中设置) 22 | // 如果未设置,则默认为中文 23 | return window.currentLang || 'zh'; 24 | } 25 | 26 | /** 27 | * 对eval加密过的JS代码进行解密 28 | * @param {String} evalJsCode - 要解密的eval加密代码 29 | * @returns {String} - 解密后的代码 30 | */ 31 | function evalDecode(evalJsCode) { 32 | // 移除开头的eval以避免重复执行 33 | evalJsCode = evalJsCode.replace(/^eval/, ""); 34 | 35 | try { 36 | return evalHolder.call(window, evalJsCode); 37 | } catch (e) { 38 | const lang = getCurrentLanguage(); 39 | console.error(lang === 'zh' ? "解密失败:" : "Decryption failed:", e); 40 | // 使用当前语言对应的错误信息 41 | const errorPrefix = lang === 'zh' ? "解密失败: " : "Decryption failed: "; 42 | return errorPrefix + e.message; 43 | } 44 | } 45 | 46 | /** 47 | * 加载示例代码 48 | */ 49 | function loadExampleCode() { 50 | return `eval(function(p,a,c,k,e,r){e=String;if(!''.replace(/^/,String)){while(c--)r[c]=k[c]||c;k=[function(e){return r[e]}];e=function(){return'\\\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p}('0.1("2");',3,3,'console|log|CC11001100'.split('|'),0,{}))`; 51 | } -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 示例eval加密的代码 3 | * 4 | * @type {string} 5 | */ 6 | const EXAMPLE_EVAL_CODE = `eval(function(p,a,c,k,e,r){e=String;if(!''.replace(/^/,String)){while(c--)r[c]=k[c]||c;k=[function(e){return r[e]}];e=function(){return'\\\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p}('0.1("2");',3,3,'console|log|CC11001100'.split('|'),0,{}))`; 7 | 8 | /** 9 | * 本地存储的Key 10 | */ 11 | const STORAGE_KEYS = { 12 | EVAL_CODE: 'JSREI-eval-decoder-eval_code-local-storage-key', 13 | LANGUAGE: 'JSREI-eval-decoder-language-key' 14 | }; 15 | 16 | // 支持的语言 17 | const SUPPORTED_LANGUAGES = ['zh', 'en']; 18 | 19 | // 语言设置 20 | const LANG = { 21 | zh: { 22 | title: "解密eval加密的代码", 23 | placeholder: "粘贴eval代码,比如:", 24 | decrypt: "eval解密", 25 | clear: "清空", 26 | loadExample: "加载示例加密代码", 27 | references: "参考链接:", 28 | groupTitle: "逆向技术交流群", 29 | scanToJoinGroup: "扫码加入逆向技术交流群:", 30 | wechatAlt: "加入微信群", 31 | qrExpired: "如群二维码过期,可以加我个人微信,发送【逆向群】拉你进群:", 32 | personalWechatAlt: "个人微信", 33 | joinTelegram: "点此或扫码加入TG交流群:", 34 | telegramAlt: "Telegram群", 35 | githubLink: "GitHub - JSREI/eval-decoder", 36 | jsPackerLink: "JS Packer 压缩原理分析", 37 | projectDocLink: "项目文档", 38 | alertEmpty: "请在文本框内粘贴被Eval加密的JavaScript代码!", 39 | alertError: "执行报错了:", 40 | forkMe: "Star me on GitHub", 41 | footer: "© 2025 Eval Decoder - 由 JSREI 提供" 42 | }, 43 | en: { 44 | title: "Decrypt Eval Encoded JavaScript", 45 | placeholder: "Paste eval code, for example:", 46 | decrypt: "Decrypt", 47 | clear: "Clear", 48 | loadExample: "Load Example", 49 | references: "References:", 50 | groupTitle: "Reverse Engineering Technology Group", 51 | scanToJoinGroup: "Scan to join the reverse engineering group:", 52 | wechatAlt: "Join WeChat Group", 53 | qrExpired: "If the group QR code expires, add my WeChat and send [Reverse Group]:", 54 | personalWechatAlt: "Personal WeChat", 55 | joinTelegram: "Click here or scan to join Telegram group:", 56 | telegramAlt: "Telegram Group", 57 | githubLink: "GitHub - JSREI/eval-decoder", 58 | jsPackerLink: "JS Packer Compression Analysis", 59 | projectDocLink: "Project Documentation", 60 | alertEmpty: "Please paste the Eval encrypted JavaScript code in the text box!", 61 | alertError: "An error occurred: ", 62 | forkMe: "Star me on GitHub", 63 | footer: "© 2025 Eval Decoder - Provided by JSREI" 64 | } 65 | }; 66 | 67 | // 当前语言 68 | let currentLang = 'zh'; 69 | 70 | // 将currentLang暴露给全局作用域 71 | window.currentLang = currentLang; 72 | 73 | /** 74 | * 检测用户浏览器语言 75 | * 返回检测到的语言代码,如果不支持则返回默认的'zh' 76 | */ 77 | function detectBrowserLanguage() { 78 | // 获取浏览器语言设置 79 | const browserLang = (navigator.language || navigator.userLanguage || 'zh').toLowerCase(); 80 | 81 | // 判断是否为英文环境 82 | if (browserLang.startsWith('en')) { 83 | return 'en'; 84 | } 85 | 86 | // 默认返回中文 87 | return 'zh'; 88 | } 89 | 90 | /** 91 | * 从本地存储加载语言设置 92 | */ 93 | function loadLanguagePreference() { 94 | // 先尝试从本地存储获取 95 | const savedLang = localStorage.getItem(STORAGE_KEYS.LANGUAGE); 96 | 97 | if (savedLang && SUPPORTED_LANGUAGES.includes(savedLang)) { 98 | return savedLang; 99 | } 100 | 101 | // 如果没有保存过语言设置,则检测浏览器语言 102 | return detectBrowserLanguage(); 103 | } 104 | 105 | /** 106 | * 保存语言设置到本地存储 107 | */ 108 | function saveLanguagePreference(lang) { 109 | localStorage.setItem(STORAGE_KEYS.LANGUAGE, lang); 110 | } 111 | 112 | /** 113 | * 加载默认的eval加密示例代码 114 | */ 115 | function loadExampleEvalCode() { 116 | document.getElementById('eval_code').value = EXAMPLE_EVAL_CODE; 117 | saveEvalCode(); 118 | } 119 | 120 | /** 121 | * 清空输入框 122 | */ 123 | function clearEvalCode() { 124 | document.getElementById('eval_code').value = ''; 125 | saveEvalCode(); 126 | } 127 | 128 | /** 129 | * 为文本输入框设置默认的示例代码 130 | */ 131 | function setPlaceholder() { 132 | const text = LANG[currentLang].placeholder + "\n" + EXAMPLE_EVAL_CODE; 133 | document.getElementById('eval_code').setAttribute("placeholder", text); 134 | } 135 | 136 | /** 137 | * 更新页面上所有文本元素的语言 138 | */ 139 | function updatePageTexts(lang) { 140 | // 更新页面标题 141 | document.title = lang === 'zh' ? 142 | "JavaScript eval解密工具 - JSREI" : 143 | "JavaScript Eval Decoder - JSREI"; 144 | 145 | // 更新HTML文档的语言属性 146 | document.documentElement.setAttribute('lang', lang); 147 | 148 | // 更新标题 149 | document.querySelector('h1').innerText = LANG[lang].title; 150 | 151 | // 更新按钮文字,使用ID选择器更可靠 152 | document.getElementById('decrypt-btn').innerText = LANG[lang].decrypt; 153 | document.getElementById('clear-btn').innerText = LANG[lang].clear; 154 | document.getElementById('example-btn').innerText = LANG[lang].loadExample; 155 | 156 | // 切换所有带语言标记的元素 157 | document.querySelectorAll('.lang-zh, .lang-en').forEach(el => { 158 | el.style.display = 'none'; 159 | }); 160 | document.querySelectorAll(`.lang-${lang}`).forEach(el => { 161 | // 如果是列表项元素,使用list-item而不是block,以确保显示列表符号 162 | if (el.tagName === 'LI') { 163 | el.style.display = 'list-item'; 164 | } else { 165 | el.style.display = 'block'; 166 | } 167 | }); 168 | 169 | // 设置图片的alt文本 170 | const imgElements = document.querySelectorAll('.group-container img'); 171 | if (imgElements.length >= 3) { 172 | imgElements[0].setAttribute('alt', LANG[lang].wechatAlt); 173 | imgElements[1].setAttribute('alt', LANG[lang].personalWechatAlt); 174 | imgElements[2].setAttribute('alt', LANG[lang].telegramAlt); 175 | } 176 | 177 | // 更新链接文本 178 | document.querySelector('.github-link').innerText = LANG[lang].githubLink; 179 | document.querySelector('.jspacker-link').innerText = LANG[lang].jsPackerLink; 180 | 181 | // 更新GitHub Ribbon 182 | const ribbon = document.getElementById('github-ribbon'); 183 | if (ribbon) { 184 | ribbon.setAttribute('data-ribbon', LANG[lang].forkMe); 185 | ribbon.setAttribute('title', LANG[lang].forkMe); 186 | ribbon.innerText = LANG[lang].forkMe; 187 | } 188 | 189 | // 更新页脚文本 190 | const footerTextElement = document.getElementById('footer-text'); 191 | if (footerTextElement) { 192 | footerTextElement.innerHTML = LANG[lang].footer; 193 | } 194 | 195 | // 更新文本框placeholder 196 | setPlaceholder(); 197 | } 198 | 199 | /** 200 | * 切换语言 201 | */ 202 | function switchLanguage(lang) { 203 | if (!SUPPORTED_LANGUAGES.includes(lang)) return; 204 | 205 | currentLang = lang; 206 | // 同步更新全局变量 207 | window.currentLang = lang; 208 | updatePageTexts(lang); 209 | saveLanguagePreference(lang); 210 | } 211 | 212 | /** 213 | * 执行解密 214 | */ 215 | function executeEval() { 216 | let evalCodeElt = document.getElementById('eval_code'); 217 | let evalCode = evalCodeElt.value; 218 | 219 | if (!evalCode) { 220 | alert(LANG[currentLang].alertEmpty); 221 | return; 222 | } 223 | 224 | try { 225 | evalCodeElt.value = evalDecode(evalCode); 226 | saveEvalCode(); 227 | } catch (e) { 228 | alert(LANG[currentLang].alertError + e); 229 | } 230 | } 231 | 232 | /** 233 | * 保存eval code到本地 234 | */ 235 | function saveEvalCode() { 236 | const evalCode = document.getElementById('eval_code').value || ''; 237 | localStorage.setItem(STORAGE_KEYS.EVAL_CODE, evalCode); 238 | } 239 | 240 | /** 241 | * 从本地存储中恢复出来上次的输入 242 | */ 243 | function recoveryEvalCode() { 244 | const evalContent = localStorage.getItem(STORAGE_KEYS.EVAL_CODE); 245 | if (!evalContent) { 246 | return; 247 | } 248 | document.getElementById('eval_code').value = evalContent; 249 | } 250 | 251 | /** 252 | * 初始化页面 253 | */ 254 | function initializePage() { 255 | // 加载用户首选语言 256 | const previousLang = localStorage.getItem(STORAGE_KEYS.LANGUAGE); 257 | currentLang = loadLanguagePreference(); 258 | 259 | // 更新页面文本 260 | updatePageTexts(currentLang); 261 | 262 | // 恢复上次输入的代码 263 | recoveryEvalCode(); 264 | 265 | // 如果是首次访问或语言与上次不同,显示通知 266 | if (!previousLang) { 267 | const message = currentLang === 'zh' 268 | ? '根据您的浏览器设置,页面已显示为中文' 269 | : 'Based on your browser settings, the page is displayed in English'; 270 | showNotification(message); 271 | } 272 | } 273 | 274 | /** 275 | * 显示简短的通知消息 276 | * @param {string} message - 要显示的消息 277 | * @param {number} duration - 显示时长(毫秒) 278 | */ 279 | function showNotification(message, duration = 3000) { 280 | // 检查是否已经存在通知元素 281 | let notification = document.querySelector('.notification'); 282 | if (notification) { 283 | document.body.removeChild(notification); 284 | } 285 | 286 | // 创建通知元素 287 | notification = document.createElement('div'); 288 | notification.className = 'notification'; 289 | notification.textContent = message; 290 | notification.style.position = 'fixed'; 291 | notification.style.top = '20px'; 292 | notification.style.left = '50%'; 293 | notification.style.transform = 'translateX(-50%)'; 294 | notification.style.padding = '10px 20px'; 295 | notification.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; 296 | notification.style.color = '#fff'; 297 | notification.style.borderRadius = '5px'; 298 | notification.style.zIndex = '9999'; 299 | notification.style.opacity = '0'; 300 | notification.style.transition = 'opacity 0.3s ease'; 301 | 302 | // 添加到页面 303 | document.body.appendChild(notification); 304 | 305 | // 显示通知 306 | setTimeout(() => { 307 | notification.style.opacity = '1'; 308 | }, 10); 309 | 310 | // 自动隐藏 311 | setTimeout(() => { 312 | notification.style.opacity = '0'; 313 | setTimeout(() => { 314 | if (notification.parentNode) { 315 | document.body.removeChild(notification); 316 | } 317 | }, 300); 318 | }, duration); 319 | } 320 | 321 | // 页面初始化 322 | document.addEventListener('DOMContentLoaded', initializePage); --------------------------------------------------------------------------------